Plugin.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\DAV\Sharing;
  4. use Sabre\DAV\Exception\BadRequest;
  5. use Sabre\DAV\Exception\Forbidden;
  6. use Sabre\DAV\INode;
  7. use Sabre\DAV\PropFind;
  8. use Sabre\DAV\Server;
  9. use Sabre\DAV\ServerPlugin;
  10. use Sabre\DAV\Xml\Element\Sharee;
  11. use Sabre\DAV\Xml\Property;
  12. use Sabre\HTTP\RequestInterface;
  13. use Sabre\HTTP\ResponseInterface;
  14. /**
  15. * This plugin implements HTTP requests and properties related to:.
  16. *
  17. * draft-pot-webdav-resource-sharing
  18. *
  19. * This specification allows people to share webdav resources with others.
  20. *
  21. * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/)
  22. * @author Evert Pot (http://evertpot.com/)
  23. * @license http://sabre.io/license/ Modified BSD License
  24. */
  25. class Plugin extends ServerPlugin
  26. {
  27. const ACCESS_NOTSHARED = 0;
  28. const ACCESS_SHAREDOWNER = 1;
  29. const ACCESS_READ = 2;
  30. const ACCESS_READWRITE = 3;
  31. const ACCESS_NOACCESS = 4;
  32. const INVITE_NORESPONSE = 1;
  33. const INVITE_ACCEPTED = 2;
  34. const INVITE_DECLINED = 3;
  35. const INVITE_INVALID = 4;
  36. /**
  37. * Reference to SabreDAV server object.
  38. *
  39. * @var Server
  40. */
  41. protected $server;
  42. /**
  43. * This method should return a list of server-features.
  44. *
  45. * This is for example 'versioning' and is added to the DAV: header
  46. * in an OPTIONS response.
  47. *
  48. * @return array
  49. */
  50. public function getFeatures()
  51. {
  52. return ['resource-sharing'];
  53. }
  54. /**
  55. * Returns a plugin name.
  56. *
  57. * Using this name other plugins will be able to access other plugins
  58. * using \Sabre\DAV\Server::getPlugin
  59. *
  60. * @return string
  61. */
  62. public function getPluginName()
  63. {
  64. return 'sharing';
  65. }
  66. /**
  67. * This initializes the plugin.
  68. *
  69. * This function is called by Sabre\DAV\Server, after
  70. * addPlugin is called.
  71. *
  72. * This method should set up the required event subscriptions.
  73. */
  74. public function initialize(Server $server)
  75. {
  76. $this->server = $server;
  77. $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';
  78. array_push(
  79. $server->protectedProperties,
  80. '{DAV:}share-mode'
  81. );
  82. $server->on('method:POST', [$this, 'httpPost']);
  83. $server->on('propFind', [$this, 'propFind']);
  84. $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
  85. $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
  86. $server->on('onBrowserPostAction', [$this, 'browserPostAction']);
  87. }
  88. /**
  89. * Updates the list of sharees on a shared resource.
  90. *
  91. * The sharees array is a list of people that are to be added modified
  92. * or removed in the list of shares.
  93. *
  94. * @param string $path
  95. * @param Sharee[] $sharees
  96. */
  97. public function shareResource($path, array $sharees)
  98. {
  99. $node = $this->server->tree->getNodeForPath($path);
  100. if (!$node instanceof ISharedNode) {
  101. throw new Forbidden('Sharing is not allowed on this node');
  102. }
  103. // Getting ACL info
  104. $acl = $this->server->getPlugin('acl');
  105. // If there's no ACL support, we allow everything
  106. if ($acl) {
  107. $acl->checkPrivileges($path, '{DAV:}share');
  108. }
  109. foreach ($sharees as $sharee) {
  110. // We're going to attempt to get a local principal uri for a share
  111. // href by emitting the getPrincipalByUri event.
  112. $principal = null;
  113. $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
  114. $sharee->principal = $principal;
  115. }
  116. $node->updateInvites($sharees);
  117. }
  118. /**
  119. * This event is triggered when properties are requested for nodes.
  120. *
  121. * This allows us to inject any sharings-specific properties.
  122. */
  123. public function propFind(PropFind $propFind, INode $node)
  124. {
  125. if ($node instanceof ISharedNode) {
  126. $propFind->handle('{DAV:}share-access', function () use ($node) {
  127. return new Property\ShareAccess($node->getShareAccess());
  128. });
  129. $propFind->handle('{DAV:}invite', function () use ($node) {
  130. return new Property\Invite($node->getInvites());
  131. });
  132. $propFind->handle('{DAV:}share-resource-uri', function () use ($node) {
  133. return new Property\Href($node->getShareResourceUri());
  134. });
  135. }
  136. }
  137. /**
  138. * We intercept this to handle POST requests on shared resources.
  139. *
  140. * @return bool|null
  141. */
  142. public function httpPost(RequestInterface $request, ResponseInterface $response)
  143. {
  144. $path = $request->getPath();
  145. $contentType = $request->getHeader('Content-Type');
  146. if (null === $contentType) {
  147. return;
  148. }
  149. // We're only interested in the davsharing content type.
  150. if (false === strpos($contentType, 'application/davsharing+xml')) {
  151. return;
  152. }
  153. $message = $this->server->xml->parse(
  154. $request->getBody(),
  155. $request->getUrl(),
  156. $documentType
  157. );
  158. switch ($documentType) {
  159. case '{DAV:}share-resource':
  160. $this->shareResource($path, $message->sharees);
  161. $response->setStatus(200);
  162. // Adding this because sending a response body may cause issues,
  163. // and I wanted some type of indicator the response was handled.
  164. $response->setHeader('X-Sabre-Status', 'everything-went-well');
  165. // Breaking the event chain
  166. return false;
  167. default:
  168. throw new BadRequest('Unexpected document type: '.$documentType.' for this Content-Type');
  169. }
  170. }
  171. /**
  172. * This method is triggered whenever a subsystem reqeuests the privileges
  173. * hat are supported on a particular node.
  174. *
  175. * We need to add a number of privileges for scheduling purposes.
  176. */
  177. public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
  178. {
  179. if ($node instanceof ISharedNode) {
  180. $supportedPrivilegeSet['{DAV:}share'] = [
  181. 'abstract' => false,
  182. 'aggregates' => [],
  183. ];
  184. }
  185. }
  186. /**
  187. * Returns a bunch of meta-data about the plugin.
  188. *
  189. * Providing this information is optional, and is mainly displayed by the
  190. * Browser plugin.
  191. *
  192. * The description key in the returned array may contain html and will not
  193. * be sanitized.
  194. *
  195. * @return array
  196. */
  197. public function getPluginInfo()
  198. {
  199. return [
  200. 'name' => $this->getPluginName(),
  201. 'description' => 'This plugin implements WebDAV resource sharing',
  202. 'link' => 'https://github.com/evert/webdav-sharing',
  203. ];
  204. }
  205. /**
  206. * This method is used to generate HTML output for the
  207. * DAV\Browser\Plugin.
  208. *
  209. * @param string $output
  210. * @param string $path
  211. *
  212. * @return bool|null
  213. */
  214. public function htmlActionsPanel(INode $node, &$output, $path)
  215. {
  216. if (!$node instanceof ISharedNode) {
  217. return;
  218. }
  219. $aclPlugin = $this->server->getPlugin('acl');
  220. if ($aclPlugin) {
  221. if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
  222. // Sharing is not permitted, we will not draw this interface.
  223. return;
  224. }
  225. }
  226. $output .= '<tr><td colspan="2"><form method="post" action="">
  227. <h3>Share this resource</h3>
  228. <input type="hidden" name="sabreAction" value="share" />
  229. <label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br />
  230. <label>Access</label>
  231. <select name="access">
  232. <option value="readwrite">Read-write</option>
  233. <option value="read">Read-only</option>
  234. <option value="no-access">Revoke access</option>
  235. </select><br />
  236. <input type="submit" value="share" />
  237. </form>
  238. </td></tr>';
  239. }
  240. /**
  241. * This method is triggered for POST actions generated by the browser
  242. * plugin.
  243. *
  244. * @param string $path
  245. * @param string $action
  246. * @param array $postVars
  247. */
  248. public function browserPostAction($path, $action, $postVars)
  249. {
  250. if ('share' !== $action) {
  251. return;
  252. }
  253. if (empty($postVars['href'])) {
  254. throw new BadRequest('The "href" POST parameter is required');
  255. }
  256. if (empty($postVars['access'])) {
  257. throw new BadRequest('The "access" POST parameter is required');
  258. }
  259. $accessMap = [
  260. 'readwrite' => self::ACCESS_READWRITE,
  261. 'read' => self::ACCESS_READ,
  262. 'no-access' => self::ACCESS_NOACCESS,
  263. ];
  264. if (!isset($accessMap[$postVars['access']])) {
  265. throw new BadRequest('The "access" POST must be readwrite, read or no-access');
  266. }
  267. $sharee = new Sharee([
  268. 'href' => $postVars['href'],
  269. 'access' => $accessMap[$postVars['access']],
  270. ]);
  271. $this->shareResource(
  272. $path,
  273. [$sharee]
  274. );
  275. return false;
  276. }
  277. }