ViewOnlyPlugin.php 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2019 ownCloud GmbH
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\DAV\DAV;
  8. use OCA\DAV\Connector\Sabre\Exception\Forbidden;
  9. use OCA\DAV\Connector\Sabre\File as DavFile;
  10. use OCA\Files_Versions\Sabre\VersionFile;
  11. use OCP\Files\Folder;
  12. use OCP\Files\NotFoundException;
  13. use OCP\Files\Storage\ISharedStorage;
  14. use Sabre\DAV\Exception\NotFound;
  15. use Sabre\DAV\Server;
  16. use Sabre\DAV\ServerPlugin;
  17. use Sabre\HTTP\RequestInterface;
  18. /**
  19. * Sabre plugin for restricting file share receiver download:
  20. */
  21. class ViewOnlyPlugin extends ServerPlugin {
  22. private ?Server $server = null;
  23. private ?Folder $userFolder;
  24. public function __construct(
  25. ?Folder $userFolder,
  26. ) {
  27. $this->userFolder = $userFolder;
  28. }
  29. /**
  30. * This initializes the plugin.
  31. *
  32. * This function is called by Sabre\DAV\Server, after
  33. * addPlugin is called.
  34. *
  35. * This method should set up the required event subscriptions.
  36. */
  37. public function initialize(Server $server): void {
  38. $this->server = $server;
  39. //priority 90 to make sure the plugin is called before
  40. //Sabre\DAV\CorePlugin::httpGet
  41. $this->server->on('method:GET', [$this, 'checkViewOnly'], 90);
  42. $this->server->on('method:COPY', [$this, 'checkViewOnly'], 90);
  43. }
  44. /**
  45. * Disallow download via DAV Api in case file being received share
  46. * and having special permission
  47. *
  48. * @throws Forbidden
  49. * @throws NotFoundException
  50. */
  51. public function checkViewOnly(RequestInterface $request): bool {
  52. $path = $request->getPath();
  53. try {
  54. assert($this->server !== null);
  55. $davNode = $this->server->tree->getNodeForPath($path);
  56. if ($davNode instanceof DavFile) {
  57. // Restrict view-only to nodes which are shared
  58. $node = $davNode->getNode();
  59. } elseif ($davNode instanceof VersionFile) {
  60. $node = $davNode->getVersion()->getSourceFile();
  61. $currentUserId = $this->userFolder?->getOwner()?->getUID();
  62. // The version source file is relative to the owner storage.
  63. // But we need the node from the current user perspective.
  64. if ($node->getOwner()->getUID() !== $currentUserId) {
  65. $nodes = $this->userFolder->getById($node->getId());
  66. $node = array_pop($nodes);
  67. if (!$node) {
  68. throw new NotFoundException('Version file not accessible by current user');
  69. }
  70. }
  71. } else {
  72. return true;
  73. }
  74. $storage = $node->getStorage();
  75. if (!$storage->instanceOfStorage(ISharedStorage::class)) {
  76. return true;
  77. }
  78. // Extract extra permissions
  79. /** @var ISharedStorage $storage */
  80. $share = $storage->getShare();
  81. $attributes = $share->getAttributes();
  82. if ($attributes === null) {
  83. return true;
  84. }
  85. // Check if read-only and on whether permission can download is both set and disabled.
  86. $canDownload = $attributes->getAttribute('permissions', 'download');
  87. if ($canDownload !== null && !$canDownload) {
  88. throw new Forbidden('Access to this shared resource has been denied because its download permission is disabled.');
  89. }
  90. } catch (NotFound $e) {
  91. // File not found
  92. }
  93. return true;
  94. }
  95. }