PublishPlugin.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OCA\DAV\CalDAV\Publishing;
  7. use OCA\DAV\CalDAV\Calendar;
  8. use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
  9. use OCP\IConfig;
  10. use OCP\IURLGenerator;
  11. use Sabre\CalDAV\Xml\Property\AllowedSharingModes;
  12. use Sabre\DAV\Exception\NotFound;
  13. use Sabre\DAV\INode;
  14. use Sabre\DAV\PropFind;
  15. use Sabre\DAV\Server;
  16. use Sabre\DAV\ServerPlugin;
  17. use Sabre\HTTP\RequestInterface;
  18. use Sabre\HTTP\ResponseInterface;
  19. class PublishPlugin extends ServerPlugin {
  20. public const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
  21. /**
  22. * Reference to SabreDAV server object.
  23. *
  24. * @var \Sabre\DAV\Server
  25. */
  26. protected $server;
  27. /**
  28. * Config instance to get instance secret.
  29. *
  30. * @var IConfig
  31. */
  32. protected $config;
  33. /**
  34. * URL Generator for absolute URLs.
  35. *
  36. * @var IURLGenerator
  37. */
  38. protected $urlGenerator;
  39. /**
  40. * PublishPlugin constructor.
  41. *
  42. * @param IConfig $config
  43. * @param IURLGenerator $urlGenerator
  44. */
  45. public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
  46. $this->config = $config;
  47. $this->urlGenerator = $urlGenerator;
  48. }
  49. /**
  50. * This method should return a list of server-features.
  51. *
  52. * This is for example 'versioning' and is added to the DAV: header
  53. * in an OPTIONS response.
  54. *
  55. * @return string[]
  56. */
  57. public function getFeatures() {
  58. // May have to be changed to be detected
  59. return ['oc-calendar-publishing', 'calendarserver-sharing'];
  60. }
  61. /**
  62. * Returns a plugin name.
  63. *
  64. * Using this name other plugins will be able to access other plugins
  65. * using Sabre\DAV\Server::getPlugin
  66. *
  67. * @return string
  68. */
  69. public function getPluginName() {
  70. return 'oc-calendar-publishing';
  71. }
  72. /**
  73. * This initializes the plugin.
  74. *
  75. * This function is called by Sabre\DAV\Server, after
  76. * addPlugin is called.
  77. *
  78. * This method should set up the required event subscriptions.
  79. *
  80. * @param Server $server
  81. */
  82. public function initialize(Server $server) {
  83. $this->server = $server;
  84. $this->server->on('method:POST', [$this, 'httpPost']);
  85. $this->server->on('propFind', [$this, 'propFind']);
  86. }
  87. public function propFind(PropFind $propFind, INode $node) {
  88. if ($node instanceof Calendar) {
  89. $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
  90. if ($node->getPublishStatus()) {
  91. // We return the publish-url only if the calendar is published.
  92. $token = $node->getPublishStatus();
  93. $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
  94. return new Publisher($publishUrl, true);
  95. }
  96. });
  97. $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) {
  98. $canShare = (!$node->isSubscription() && $node->canWrite());
  99. $canPublish = (!$node->isSubscription() && $node->canWrite());
  100. if ($this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes') {
  101. $canShare = $canShare && ($node->getOwner() === $node->getPrincipalURI());
  102. $canPublish = $canPublish && ($node->getOwner() === $node->getPrincipalURI());
  103. }
  104. return new AllowedSharingModes($canShare, $canPublish);
  105. });
  106. }
  107. }
  108. /**
  109. * We intercept this to handle POST requests on calendars.
  110. *
  111. * @param RequestInterface $request
  112. * @param ResponseInterface $response
  113. *
  114. * @return void|bool
  115. */
  116. public function httpPost(RequestInterface $request, ResponseInterface $response) {
  117. $path = $request->getPath();
  118. // Only handling xml
  119. $contentType = (string) $request->getHeader('Content-Type');
  120. if (!str_contains($contentType, 'application/xml') && !str_contains($contentType, 'text/xml')) {
  121. return;
  122. }
  123. // Making sure the node exists
  124. try {
  125. $node = $this->server->tree->getNodeForPath($path);
  126. } catch (NotFound $e) {
  127. return;
  128. }
  129. $requestBody = $request->getBodyAsString();
  130. // If this request handler could not deal with this POST request, it
  131. // will return 'null' and other plugins get a chance to handle the
  132. // request.
  133. //
  134. // However, we already requested the full body. This is a problem,
  135. // because a body can only be read once. This is why we preemptively
  136. // re-populated the request body with the existing data.
  137. $request->setBody($requestBody);
  138. $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
  139. switch ($documentType) {
  140. case '{'.self::NS_CALENDARSERVER.'}publish-calendar':
  141. // We can only deal with IShareableCalendar objects
  142. if (!$node instanceof Calendar) {
  143. return;
  144. }
  145. $this->server->transactionType = 'post-publish-calendar';
  146. // Getting ACL info
  147. $acl = $this->server->getPlugin('acl');
  148. // If there's no ACL support, we allow everything
  149. if ($acl) {
  150. /** @var \Sabre\DAVACL\Plugin $acl */
  151. $acl->checkPrivileges($path, '{DAV:}write');
  152. $limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
  153. $isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
  154. if ($limitSharingToOwner && !$isOwner) {
  155. return;
  156. }
  157. }
  158. $node->setPublishStatus(true);
  159. // iCloud sends back the 202, so we will too.
  160. $response->setStatus(202);
  161. // Adding this because sending a response body may cause issues,
  162. // and I wanted some type of indicator the response was handled.
  163. $response->setHeader('X-Sabre-Status', 'everything-went-well');
  164. // Breaking the event chain
  165. return false;
  166. case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar':
  167. // We can only deal with IShareableCalendar objects
  168. if (!$node instanceof Calendar) {
  169. return;
  170. }
  171. $this->server->transactionType = 'post-unpublish-calendar';
  172. // Getting ACL info
  173. $acl = $this->server->getPlugin('acl');
  174. // If there's no ACL support, we allow everything
  175. if ($acl) {
  176. /** @var \Sabre\DAVACL\Plugin $acl */
  177. $acl->checkPrivileges($path, '{DAV:}write');
  178. $limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
  179. $isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
  180. if ($limitSharingToOwner && !$isOwner) {
  181. return;
  182. }
  183. }
  184. $node->setPublishStatus(false);
  185. $response->setStatus(200);
  186. // Adding this because sending a response body may cause issues,
  187. // and I wanted some type of indicator the response was handled.
  188. $response->setHeader('X-Sabre-Status', 'everything-went-well');
  189. // Breaking the event chain
  190. return false;
  191. }
  192. }
  193. }