DavAclPlugin.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\DAV\Connector\Sabre;
  8. use OCA\DAV\CalDAV\CachedSubscription;
  9. use OCA\DAV\CalDAV\Calendar;
  10. use OCA\DAV\CardDAV\AddressBook;
  11. use Sabre\CalDAV\Principal\User;
  12. use Sabre\DAV\Exception\Forbidden;
  13. use Sabre\DAV\Exception\NotFound;
  14. use Sabre\DAV\INode;
  15. use Sabre\DAV\PropFind;
  16. use Sabre\HTTP\RequestInterface;
  17. use Sabre\HTTP\ResponseInterface;
  18. /**
  19. * Class DavAclPlugin is a wrapper around \Sabre\DAVACL\Plugin that returns 404
  20. * responses in case the resource to a response has been forbidden instead of
  21. * a 403. This is used to prevent enumeration of valid resources.
  22. *
  23. * @see https://github.com/owncloud/core/issues/22578
  24. * @package OCA\DAV\Connector\Sabre
  25. */
  26. class DavAclPlugin extends \Sabre\DAVACL\Plugin {
  27. public function __construct() {
  28. $this->hideNodesFromListings = true;
  29. $this->allowUnauthenticatedAccess = false;
  30. }
  31. public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {
  32. $access = parent::checkPrivileges($uri, $privileges, $recursion, false);
  33. if ($access === false && $throwExceptions) {
  34. /** @var INode $node */
  35. $node = $this->server->tree->getNodeForPath($uri);
  36. switch (get_class($node)) {
  37. case AddressBook::class:
  38. $type = 'Addressbook';
  39. break;
  40. case Calendar::class:
  41. case CachedSubscription::class:
  42. $type = 'Calendar';
  43. break;
  44. default:
  45. $type = 'Node';
  46. break;
  47. }
  48. if ($this->getCurrentUserPrincipal() === $node->getOwner()) {
  49. throw new Forbidden('Access denied');
  50. } else {
  51. throw new NotFound(
  52. sprintf(
  53. "%s with name '%s' could not be found",
  54. $type,
  55. $node->getName()
  56. )
  57. );
  58. }
  59. }
  60. return $access;
  61. }
  62. public function propFind(PropFind $propFind, INode $node) {
  63. if ($node instanceof Node) {
  64. // files don't use dav acls
  65. return;
  66. }
  67. // If the node is neither readable nor writable then fail unless its of
  68. // the standard user-principal
  69. if (!($node instanceof User)) {
  70. $path = $propFind->getPath();
  71. $readPermissions = $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false);
  72. $writePermissions = $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, false);
  73. if ($readPermissions === false && $writePermissions === false) {
  74. $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, true);
  75. $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, true);
  76. }
  77. }
  78. return parent::propFind($propFind, $node);
  79. }
  80. public function beforeMethod(RequestInterface $request, ResponseInterface $response) {
  81. $path = $request->getPath();
  82. // prevent the plugin from causing an unneeded overhead for file requests
  83. if (str_starts_with($path, 'files/')) {
  84. return;
  85. }
  86. parent::beforeMethod($request, $response);
  87. if (!str_starts_with($path, 'addressbooks/') && !str_starts_with($path, 'calendars/')) {
  88. return;
  89. }
  90. [$parentName] = \Sabre\Uri\split($path);
  91. if ($request->getMethod() === 'REPORT') {
  92. // is calendars/users/bob or addressbooks/users/bob readable?
  93. $this->checkPrivileges($parentName, '{DAV:}read');
  94. } elseif ($request->getMethod() === 'MKCALENDAR' || $request->getMethod() === 'MKCOL') {
  95. // is calendars/users/bob or addressbooks/users/bob writeable?
  96. $this->checkPrivileges($parentName, '{DAV:}write');
  97. }
  98. }
  99. }