You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SystemTagPlugin.php 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Lukas Reschke <lukas@statuscode.ch>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. * @author Thomas Müller <thomas.mueller@tmit.eu>
  9. * @author Vincent Petry <vincent@nextcloud.com>
  10. *
  11. * @license AGPL-3.0
  12. *
  13. * This code is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License, version 3,
  15. * as published by the Free Software Foundation.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License, version 3,
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>
  24. *
  25. */
  26. namespace OCA\DAV\SystemTag;
  27. use OCP\IGroupManager;
  28. use OCP\IUserSession;
  29. use OCP\SystemTag\ISystemTag;
  30. use OCP\SystemTag\ISystemTagManager;
  31. use OCP\SystemTag\TagAlreadyExistsException;
  32. use Sabre\DAV\Exception\BadRequest;
  33. use Sabre\DAV\Exception\Conflict;
  34. use Sabre\DAV\Exception\Forbidden;
  35. use Sabre\DAV\Exception\UnsupportedMediaType;
  36. use Sabre\DAV\PropFind;
  37. use Sabre\DAV\PropPatch;
  38. use Sabre\HTTP\RequestInterface;
  39. use Sabre\HTTP\ResponseInterface;
  40. /**
  41. * Sabre plugin to handle system tags:
  42. *
  43. * - makes it possible to create new tags with POST operation
  44. * - get/set Webdav properties for tags
  45. *
  46. */
  47. class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
  48. // namespace
  49. public const NS_OWNCLOUD = 'http://owncloud.org/ns';
  50. public const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
  51. public const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
  52. public const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
  53. public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
  54. public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
  55. public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
  56. /**
  57. * @var \Sabre\DAV\Server $server
  58. */
  59. private $server;
  60. /**
  61. * @var ISystemTagManager
  62. */
  63. protected $tagManager;
  64. /**
  65. * @var IUserSession
  66. */
  67. protected $userSession;
  68. /**
  69. * @var IGroupManager
  70. */
  71. protected $groupManager;
  72. /**
  73. * @param ISystemTagManager $tagManager tag manager
  74. * @param IGroupManager $groupManager
  75. * @param IUserSession $userSession
  76. */
  77. public function __construct(ISystemTagManager $tagManager,
  78. IGroupManager $groupManager,
  79. IUserSession $userSession) {
  80. $this->tagManager = $tagManager;
  81. $this->userSession = $userSession;
  82. $this->groupManager = $groupManager;
  83. }
  84. /**
  85. * This initializes the plugin.
  86. *
  87. * This function is called by \Sabre\DAV\Server, after
  88. * addPlugin is called.
  89. *
  90. * This method should set up the required event subscriptions.
  91. *
  92. * @param \Sabre\DAV\Server $server
  93. * @return void
  94. */
  95. public function initialize(\Sabre\DAV\Server $server) {
  96. $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
  97. $server->protectedProperties[] = self::ID_PROPERTYNAME;
  98. $server->on('propFind', [$this, 'handleGetProperties']);
  99. $server->on('propPatch', [$this, 'handleUpdateProperties']);
  100. $server->on('method:POST', [$this, 'httpPost']);
  101. $this->server = $server;
  102. }
  103. /**
  104. * POST operation on system tag collections
  105. *
  106. * @param RequestInterface $request request object
  107. * @param ResponseInterface $response response object
  108. * @return null|false
  109. */
  110. public function httpPost(RequestInterface $request, ResponseInterface $response) {
  111. $path = $request->getPath();
  112. // Making sure the node exists
  113. $node = $this->server->tree->getNodeForPath($path);
  114. if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
  115. $data = $request->getBodyAsString();
  116. $tag = $this->createTag($data, $request->getHeader('Content-Type'));
  117. if ($node instanceof SystemTagsObjectMappingCollection) {
  118. // also add to collection
  119. $node->createFile($tag->getId());
  120. $url = $request->getBaseUrl() . 'systemtags/';
  121. } else {
  122. $url = $request->getUrl();
  123. }
  124. if ($url[strlen($url) - 1] !== '/') {
  125. $url .= '/';
  126. }
  127. $response->setHeader('Content-Location', $url . $tag->getId());
  128. // created
  129. $response->setStatus(201);
  130. return false;
  131. }
  132. }
  133. /**
  134. * Creates a new tag
  135. *
  136. * @param string $data JSON encoded string containing the properties of the tag to create
  137. * @param string $contentType content type of the data
  138. * @return ISystemTag newly created system tag
  139. *
  140. * @throws BadRequest if a field was missing
  141. * @throws Conflict if a tag with the same properties already exists
  142. * @throws UnsupportedMediaType if the content type is not supported
  143. */
  144. private function createTag($data, $contentType = 'application/json') {
  145. if (explode(';', $contentType)[0] === 'application/json') {
  146. $data = json_decode($data, true);
  147. } else {
  148. throw new UnsupportedMediaType();
  149. }
  150. if (!isset($data['name'])) {
  151. throw new BadRequest('Missing "name" attribute');
  152. }
  153. $tagName = $data['name'];
  154. $userVisible = true;
  155. $userAssignable = true;
  156. if (isset($data['userVisible'])) {
  157. $userVisible = (bool)$data['userVisible'];
  158. }
  159. if (isset($data['userAssignable'])) {
  160. $userAssignable = (bool)$data['userAssignable'];
  161. }
  162. $groups = [];
  163. if (isset($data['groups'])) {
  164. $groups = $data['groups'];
  165. if (is_string($groups)) {
  166. $groups = explode('|', $groups);
  167. }
  168. }
  169. if ($userVisible === false || $userAssignable === false || !empty($groups)) {
  170. if (!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
  171. throw new BadRequest('Not sufficient permissions');
  172. }
  173. }
  174. try {
  175. $tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
  176. if (!empty($groups)) {
  177. $this->tagManager->setTagGroups($tag, $groups);
  178. }
  179. return $tag;
  180. } catch (TagAlreadyExistsException $e) {
  181. throw new Conflict('Tag already exists', 0, $e);
  182. }
  183. }
  184. /**
  185. * Retrieves system tag properties
  186. *
  187. * @param PropFind $propFind
  188. * @param \Sabre\DAV\INode $node
  189. */
  190. public function handleGetProperties(
  191. PropFind $propFind,
  192. \Sabre\DAV\INode $node
  193. ) {
  194. if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
  195. return;
  196. }
  197. $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
  198. return $node->getSystemTag()->getId();
  199. });
  200. $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
  201. return $node->getSystemTag()->getName();
  202. });
  203. $propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
  204. return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
  205. });
  206. $propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
  207. // this is the tag's inherent property "is user assignable"
  208. return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
  209. });
  210. $propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
  211. // this is the effective permission for the current user
  212. return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
  213. });
  214. $propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
  215. if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
  216. // property only available for admins
  217. throw new Forbidden();
  218. }
  219. $groups = [];
  220. // no need to retrieve groups for namespaces that don't qualify
  221. if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) {
  222. $groups = $this->tagManager->getTagGroups($node->getSystemTag());
  223. }
  224. return implode('|', $groups);
  225. });
  226. }
  227. /**
  228. * Updates tag attributes
  229. *
  230. * @param string $path
  231. * @param PropPatch $propPatch
  232. *
  233. * @return void
  234. */
  235. public function handleUpdateProperties($path, PropPatch $propPatch) {
  236. $node = $this->server->tree->getNodeForPath($path);
  237. if (!($node instanceof SystemTagNode)) {
  238. return;
  239. }
  240. $propPatch->handle([
  241. self::DISPLAYNAME_PROPERTYNAME,
  242. self::USERVISIBLE_PROPERTYNAME,
  243. self::USERASSIGNABLE_PROPERTYNAME,
  244. self::GROUPS_PROPERTYNAME,
  245. ], function ($props) use ($node) {
  246. $tag = $node->getSystemTag();
  247. $name = $tag->getName();
  248. $userVisible = $tag->isUserVisible();
  249. $userAssignable = $tag->isUserAssignable();
  250. $updateTag = false;
  251. if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
  252. $name = $props[self::DISPLAYNAME_PROPERTYNAME];
  253. $updateTag = true;
  254. }
  255. if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
  256. $propValue = $props[self::USERVISIBLE_PROPERTYNAME];
  257. $userVisible = ($propValue !== 'false' && $propValue !== '0');
  258. $updateTag = true;
  259. }
  260. if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
  261. $propValue = $props[self::USERASSIGNABLE_PROPERTYNAME];
  262. $userAssignable = ($propValue !== 'false' && $propValue !== '0');
  263. $updateTag = true;
  264. }
  265. if (isset($props[self::GROUPS_PROPERTYNAME])) {
  266. if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
  267. // property only available for admins
  268. throw new Forbidden();
  269. }
  270. $propValue = $props[self::GROUPS_PROPERTYNAME];
  271. $groupIds = explode('|', $propValue);
  272. $this->tagManager->setTagGroups($tag, $groupIds);
  273. }
  274. if ($updateTag) {
  275. $node->update($name, $userVisible, $userAssignable);
  276. }
  277. return true;
  278. });
  279. }
  280. }