FilesReportPlugin.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. /**
  3. * @author Joas Schilling <nickvergessen@owncloud.com>
  4. * @author Vincent Petry <pvince81@owncloud.com>
  5. *
  6. * @copyright Copyright (c) 2016, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. namespace OCA\DAV\Connector\Sabre;
  23. use OC\Files\View;
  24. use Sabre\DAV\Exception\NotFound;
  25. use Sabre\DAV\Exception\PreconditionFailed;
  26. use Sabre\DAV\Exception\ReportNotSupported;
  27. use Sabre\DAV\Exception\BadRequest;
  28. use Sabre\DAV\ServerPlugin;
  29. use Sabre\DAV\Tree;
  30. use Sabre\DAV\Xml\Element\Response;
  31. use Sabre\DAV\Xml\Response\MultiStatus;
  32. use Sabre\DAV\PropFind;
  33. use OCP\SystemTag\ISystemTagObjectMapper;
  34. use OCP\IUserSession;
  35. use OCP\Files\Folder;
  36. use OCP\IGroupManager;
  37. use OCP\SystemTag\ISystemTagManager;
  38. use OCP\SystemTag\TagNotFoundException;
  39. class FilesReportPlugin extends ServerPlugin {
  40. // namespace
  41. const NS_OWNCLOUD = 'http://owncloud.org/ns';
  42. const REPORT_NAME = '{http://owncloud.org/ns}filter-files';
  43. const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
  44. /**
  45. * Reference to main server object
  46. *
  47. * @var \Sabre\DAV\Server
  48. */
  49. private $server;
  50. /**
  51. * @var Tree
  52. */
  53. private $tree;
  54. /**
  55. * @var View
  56. */
  57. private $fileView;
  58. /**
  59. * @var ISystemTagManager
  60. */
  61. private $tagManager;
  62. /**
  63. * @var ISystemTagObjectMapper
  64. */
  65. private $tagMapper;
  66. /**
  67. * @var IUserSession
  68. */
  69. private $userSession;
  70. /**
  71. * @var IGroupManager
  72. */
  73. private $groupManager;
  74. /**
  75. * @var Folder
  76. */
  77. private $userFolder;
  78. /**
  79. * @param Tree $tree
  80. * @param View $view
  81. */
  82. public function __construct(Tree $tree,
  83. View $view,
  84. ISystemTagManager $tagManager,
  85. ISystemTagObjectMapper $tagMapper,
  86. IUserSession $userSession,
  87. IGroupManager $groupManager,
  88. Folder $userFolder
  89. ) {
  90. $this->tree = $tree;
  91. $this->fileView = $view;
  92. $this->tagManager = $tagManager;
  93. $this->tagMapper = $tagMapper;
  94. $this->userSession = $userSession;
  95. $this->groupManager = $groupManager;
  96. $this->userFolder = $userFolder;
  97. }
  98. /**
  99. * This initializes the plugin.
  100. *
  101. * This function is called by \Sabre\DAV\Server, after
  102. * addPlugin is called.
  103. *
  104. * This method should set up the required event subscriptions.
  105. *
  106. * @param \Sabre\DAV\Server $server
  107. * @return void
  108. */
  109. public function initialize(\Sabre\DAV\Server $server) {
  110. $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
  111. $this->server = $server;
  112. $this->server->on('report', array($this, 'onReport'));
  113. }
  114. /**
  115. * Returns a list of reports this plugin supports.
  116. *
  117. * This will be used in the {DAV:}supported-report-set property.
  118. *
  119. * @param string $uri
  120. * @return array
  121. */
  122. public function getSupportedReportSet($uri) {
  123. return [self::REPORT_NAME];
  124. }
  125. /**
  126. * REPORT operations to look for files
  127. *
  128. * @param string $reportName
  129. * @param [] $report
  130. * @param string $uri
  131. * @return bool
  132. * @throws NotFound
  133. * @throws ReportNotSupported
  134. */
  135. public function onReport($reportName, $report, $uri) {
  136. $reportTargetNode = $this->server->tree->getNodeForPath($uri);
  137. if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
  138. throw new ReportNotSupported();
  139. }
  140. $ns = '{' . $this::NS_OWNCLOUD . '}';
  141. $requestedProps = [];
  142. $filterRules = [];
  143. // parse report properties and gather filter info
  144. foreach ($report as $reportProps) {
  145. $name = $reportProps['name'];
  146. if ($name === $ns . 'filter-rules') {
  147. $filterRules = $reportProps['value'];
  148. } else if ($name === '{DAV:}prop') {
  149. // propfind properties
  150. foreach ($reportProps['value'] as $propVal) {
  151. $requestedProps[] = $propVal['name'];
  152. }
  153. }
  154. }
  155. if (empty($filterRules)) {
  156. // an empty filter would return all existing files which would be slow
  157. throw new BadRequest('Missing filter-rule block in request');
  158. }
  159. // gather all file ids matching filter
  160. try {
  161. $resultFileIds = $this->processFilterRules($filterRules);
  162. } catch (TagNotFoundException $e) {
  163. throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
  164. }
  165. // find sabre nodes by file id, restricted to the root node path
  166. $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
  167. $responses = $this->prepareResponses($requestedProps, $results);
  168. $xml = $this->server->xml->write(
  169. '{DAV:}multistatus',
  170. new MultiStatus($responses)
  171. );
  172. $this->server->httpResponse->setStatus(207);
  173. $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
  174. $this->server->httpResponse->setBody($xml);
  175. return false;
  176. }
  177. /**
  178. * Find file ids matching the given filter rules
  179. *
  180. * @param array $filterRules
  181. * @return array array of unique file id results
  182. *
  183. * @throws TagNotFoundException whenever a tag was not found
  184. */
  185. protected function processFilterRules($filterRules) {
  186. $ns = '{' . $this::NS_OWNCLOUD . '}';
  187. $resultFileIds = null;
  188. $systemTagIds = [];
  189. foreach ($filterRules as $filterRule) {
  190. if ($filterRule['name'] === $ns . 'systemtag') {
  191. $systemTagIds[] = $filterRule['value'];
  192. }
  193. }
  194. // check user permissions, if applicable
  195. if (!$this->isAdmin()) {
  196. // check visibility/permission
  197. $tags = $this->tagManager->getTagsByIds($systemTagIds);
  198. $unknownTagIds = [];
  199. foreach ($tags as $tag) {
  200. if (!$tag->isUserVisible()) {
  201. $unknownTagIds[] = $tag->getId();
  202. }
  203. }
  204. if (!empty($unknownTagIds)) {
  205. throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
  206. }
  207. }
  208. // fetch all file ids and intersect them
  209. foreach ($systemTagIds as $systemTagId) {
  210. $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
  211. if (empty($fileIds)) {
  212. // This tag has no files, nothing can ever show up
  213. return [];
  214. }
  215. // first run ?
  216. if ($resultFileIds === null) {
  217. $resultFileIds = $fileIds;
  218. } else {
  219. $resultFileIds = array_intersect($resultFileIds, $fileIds);
  220. }
  221. if (empty($resultFileIds)) {
  222. // Empty intersection, nothing can show up anymore
  223. return [];
  224. }
  225. }
  226. return $resultFileIds;
  227. }
  228. /**
  229. * Prepare propfind response for the given nodes
  230. *
  231. * @param string[] $requestedProps requested properties
  232. * @param Node[] nodes nodes for which to fetch and prepare responses
  233. * @return Response[]
  234. */
  235. public function prepareResponses($requestedProps, $nodes) {
  236. $responses = [];
  237. foreach ($nodes as $node) {
  238. $propFind = new PropFind($node->getPath(), $requestedProps);
  239. $this->server->getPropertiesByNode($propFind, $node);
  240. // copied from Sabre Server's getPropertiesForPath
  241. $result = $propFind->getResultForMultiStatus();
  242. $result['href'] = $propFind->getPath();
  243. $resourceType = $this->server->getResourceTypeForNode($node);
  244. if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
  245. $result['href'] .= '/';
  246. }
  247. $responses[] = new Response(
  248. rtrim($this->server->getBaseUri(), '/') . $node->getPath(),
  249. $result,
  250. 200
  251. );
  252. }
  253. return $responses;
  254. }
  255. /**
  256. * Find Sabre nodes by file ids
  257. *
  258. * @param Node $rootNode root node for search
  259. * @param array $fileIds file ids
  260. * @return Node[] array of Sabre nodes
  261. */
  262. public function findNodesByFileIds($rootNode, $fileIds) {
  263. $folder = $this->userFolder;
  264. if (trim($rootNode->getPath(), '/') !== '') {
  265. $folder = $folder->get($rootNode->getPath());
  266. }
  267. $results = [];
  268. foreach ($fileIds as $fileId) {
  269. $entry = $folder->getById($fileId);
  270. if ($entry) {
  271. $entry = current($entry);
  272. if ($entry instanceof \OCP\Files\File) {
  273. $results[] = new File($this->fileView, $entry);
  274. } else if ($entry instanceof \OCP\Files\Folder) {
  275. $results[] = new Directory($this->fileView, $entry);
  276. }
  277. }
  278. }
  279. return $results;
  280. }
  281. /**
  282. * Returns whether the currently logged in user is an administrator
  283. */
  284. private function isAdmin() {
  285. $user = $this->userSession->getUser();
  286. if ($user !== null) {
  287. return $this->groupManager->isAdmin($user->getUID());
  288. }
  289. return false;
  290. }
  291. }