Skip to content

DAV permissions for read only single file shares indicate updatable #53041

@juliusknorr

Description

@juliusknorr

This seems like a bug that i noticed when trying to check for the permissions using @nextcloud/files where it turned out my file action did not hide on read only shares.

Steps to reproduce

  • as userA: Share a single file as view only (permission: 1) to userB
  • as userB: Check the webdav response and see <oc:permissions>SGDNV</oc:permissions> indicating that the file can be updated

Additional context

  • This does not happen with folder shares, there both the root mount and files within have correct webdav permissions exposed
  • I already spend some time stepping through code
    • Permissions in the FileInfo in
      $permissions = $info->getPermissions();
      are already wrong with 11 (unsure where that is originating from, but my guess is somewhere in
      public function getDirectoryContent($directory, $mimetype_filter = '', ?\OCP\Files\FileInfo $directoryInfo = null) {
      $this->assertPathLength($directory);
      if (!Filesystem::isValidPath($directory)) {
      return [];
      }
      $path = $this->getAbsolutePath($directory);
      $path = Filesystem::normalizePath($path);
      $mount = $this->getMount($directory);
      $storage = $mount->getStorage();
      $internalPath = $mount->getInternalPath($path);
      if (!$storage) {
      return [];
      }
      $cache = $storage->getCache($internalPath);
      $user = \OC_User::getUser();
      if (!$directoryInfo) {
      $data = $this->getCacheEntry($storage, $internalPath, $directory);
      if (!$data instanceof ICacheEntry || !isset($data['fileid'])) {
      return [];
      }
      } else {
      $data = $directoryInfo;
      }
      if (!($data->getPermissions() & Constants::PERMISSION_READ)) {
      return [];
      }
      $folderId = $data->getId();
      $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
      $sharingDisabled = \OCP\Util::isSharingDisabledForUser();
      $fileNames = array_map(function (ICacheEntry $content) {
      return $content->getName();
      }, $contents);
      /**
      * @var \OC\Files\FileInfo[] $fileInfos
      */
      $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
      if ($sharingDisabled) {
      $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
      }
      $ownerId = $storage->getOwner($content['path']);
      if ($ownerId !== false) {
      $owner = $this->getUserObjectForOwner($ownerId);
      } else {
      $owner = null;
      }
      return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
      }, $contents);
      $files = array_combine($fileNames, $fileInfos);
      //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
      $mounts = Filesystem::getMountManager()->findIn($path);
      // make sure nested mounts are sorted after their parent mounts
      // otherwise doesn't propagate the etag across storage boundaries correctly
      usort($mounts, function (IMountPoint $a, IMountPoint $b) {
      return $a->getMountPoint() <=> $b->getMountPoint();
      });
      $dirLength = strlen($path);
      foreach ($mounts as $mount) {
      $mountPoint = $mount->getMountPoint();
      $subStorage = $mount->getStorage();
      if ($subStorage) {
      $subCache = $subStorage->getCache('');
      $rootEntry = $subCache->get('');
      if (!$rootEntry) {
      $subScanner = $subStorage->getScanner();
      try {
      $subScanner->scanFile('');
      } catch (\OCP\Files\StorageNotAvailableException $e) {
      continue;
      } catch (\OCP\Files\StorageInvalidException $e) {
      continue;
      } catch (\Exception $e) {
      // sometimes when the storage is not available it can be any exception
      $this->logger->error('Exception while scanning storage "' . $subStorage->getId() . '"', [
      'exception' => $e,
      'app' => 'core',
      ]);
      continue;
      }
      $rootEntry = $subCache->get('');
      }
      if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) {
      $relativePath = trim(substr($mountPoint, $dirLength), '/');
      if ($pos = strpos($relativePath, '/')) {
      //mountpoint inside subfolder add size to the correct folder
      $entryName = substr($relativePath, 0, $pos);
      // Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
      if (!isset($files[$entryName])) {
      try {
      [$storage, ] = $this->resolvePath($path . '/' . $entryName);
      // make sure we can create the mountpoint folder, even if the user has a quota of 0
      if ($storage->instanceOfStorage(Quota::class)) {
      $storage->enableQuota(false);
      }
      if ($this->mkdir($path . '/' . $entryName) !== false) {
      $info = $this->getFileInfo($path . '/' . $entryName);
      if ($info !== false) {
      $files[$entryName] = $info;
      }
      }
      if ($storage->instanceOfStorage(Quota::class)) {
      $storage->enableQuota(true);
      }
      } catch (\Exception $e) {
      // Creating the parent folder might not be possible, for example due to a lack of permissions.
      $this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
      }
      }
      if (isset($files[$entryName])) {
      $files[$entryName]->addSubEntry($rootEntry, $mountPoint);
      }
      } else { //mountpoint in this folder, add an entry for it
      $rootEntry['name'] = $relativePath;
      $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
      $permissions = $rootEntry['permissions'];
      // do not allow renaming/deleting the mount point if they are not shared files/folders
      // for shared files/folders we use the permissions given by the owner
      if ($mount instanceof MoveableMount) {
      $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
      } else {
      $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
      }
      $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
      // if sharing was disabled for the user we remove the share permissions
      if ($sharingDisabled) {
      $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
      }
      $ownerId = $subStorage->getOwner('');
      if ($ownerId !== false) {
      $owner = $this->getUserObjectForOwner($ownerId);
      } else {
      $owner = null;
      }
      $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
      }
      }
      }
      }
      if ($mimetype_filter) {
      $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
      if (strpos($mimetype_filter, '/')) {
      return $file->getMimetype() === $mimetype_filter;
      } else {
      return $file->getMimePart() === $mimetype_filter;
      }
      });
      }
      return array_values($files);
      }
      )

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending check for reproducibility or if it fits our roadmap32-feedbackbugfeature: davfeature: sharinggood first issueSmall tasks with clear documentation about how and in which place you need to fix things in.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions