summaryrefslogtreecommitdiffstats
path: root/apps/files_sharing
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2019-10-04 22:54:04 +0200
committerGitHub <noreply@github.com>2019-10-04 22:54:04 +0200
commit2169c261957ee447cfdf27dac593508d8ecc16e9 (patch)
treec189db36916df556e2476ec38ea036d9a2346269 /apps/files_sharing
parent2507f26428873e12976c9e3b9e0a0fb7d8429fdd (diff)
parentff895abac081ffd53b9d1509565e9dfe923b6d60 (diff)
downloadnextcloud-server-2169c261957ee447cfdf27dac593508d8ecc16e9.tar.gz
nextcloud-server-2169c261957ee447cfdf27dac593508d8ecc16e9.zip
Fix shares read permissions (#16761)
Fix shares read permissions
Diffstat (limited to 'apps/files_sharing')
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php153
-rw-r--r--apps/files_sharing/tests/Controller/ShareAPIControllerTest.php268
2 files changed, 403 insertions, 18 deletions
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index 5bd059219bb..cde4f93a0f0 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -305,13 +305,13 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}
- if ($this->canAccessShare($share)) {
- try {
+ try {
+ if ($this->canAccessShare($share)) {
$share = $this->formatShare($share);
return new DataResponse([$share]);
- } catch (NotFoundException $e) {
- //Fall trough
}
+ } catch (NotFoundException $e) {
+ // Fall trough
}
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
@@ -336,21 +336,24 @@ class ShareAPIController extends OCSController {
try {
$this->lock($share->getNode());
} catch (LockedException $e) {
- throw new OCSNotFoundException($this->l->t('could not delete share'));
+ throw new OCSNotFoundException($this->l->t('Could not delete share'));
}
if (!$this->canAccessShare($share)) {
- throw new OCSNotFoundException($this->l->t('Could not delete share'));
+ throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}
- if ((
- $share->getShareType() === Share::SHARE_TYPE_GROUP
- || $share->getShareType() === Share::SHARE_TYPE_ROOM
- )
- && $share->getShareOwner() !== $this->currentUser
- && $share->getSharedBy() !== $this->currentUser) {
+ // if it's a group share or a room share
+ // we don't delete the share, but only the
+ // mount point. Allowing it to be restored
+ // from the deleted shares
+ if ($this->canDeleteShareFromSelf($share)) {
$this->shareManager->deleteFromSelf($share, $this->currentUser);
} else {
+ if (!$this->canDeleteShare($share)) {
+ throw new OCSForbiddenException($this->l->t('Could not delete share'));
+ }
+
$this->shareManager->deleteShare($share);
}
@@ -500,7 +503,6 @@ class ShareAPIController extends OCSController {
}
}
-
if ($sendPasswordByTalk === 'true') {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$path->getPath()]));
@@ -823,7 +825,7 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}
- if ($share->getShareOwner() !== $this->currentUser && $share->getSharedBy() !== $this->currentUser) {
+ if (!$this->canEditShare($share)) {
throw new OCSForbiddenException('You are not allowed to edit incoming shares');
}
@@ -981,6 +983,13 @@ class ShareAPIController extends OCSController {
}
/**
+ * Does the user have read permission on the share
+ *
+ * @param \OCP\Share\IShare $share the share to check
+ * @param boolean $checkGroups check groups as well?
+ * @return boolean
+ * @throws NotFoundException
+ *
* @suppress PhanUndeclaredClassMethod
*/
protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
@@ -995,12 +1004,21 @@ class ShareAPIController extends OCSController {
return true;
}
- // If the share is shared with you (or a group you are a member of)
+ // If the share is shared with you, you can access it!
if ($share->getShareType() === Share::SHARE_TYPE_USER
&& $share->getSharedWith() === $this->currentUser) {
return true;
}
+ // Have reshare rights on the shared file/folder ?
+ // Does the currentUser have access to the shared file?
+ $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
+ $files = $userFolder->getById($share->getNodeId());
+ if (!empty($files) && $this->shareProviderResharingRights($this->currentUser, $share, $files[0])) {
+ return true;
+ }
+
+ // If in the recipient group, you can see the share
if ($checkGroups && $share->getShareType() === Share::SHARE_TYPE_GROUP) {
$sharedWith = $this->groupManager->get($share->getSharedWith());
$user = $this->userManager->get($this->currentUser);
@@ -1026,6 +1044,110 @@ class ShareAPIController extends OCSController {
}
/**
+ * Does the user have edit permission on the share
+ *
+ * @param \OCP\Share\IShare $share the share to check
+ * @return boolean
+ */
+ protected function canEditShare(\OCP\Share\IShare $share): bool {
+ // A file with permissions 0 can't be accessed by us. So Don't show it
+ if ($share->getPermissions() === 0) {
+ return false;
+ }
+
+ // The owner of the file and the creator of the share
+ // can always edit the share
+ if ($share->getShareOwner() === $this->currentUser ||
+ $share->getSharedBy() === $this->currentUser
+ ) {
+ return true;
+ }
+
+ //! we do NOT support some kind of `admin` in groups.
+ //! You cannot edit shares shared to a group you're
+ //! a member of if you're not the share owner or the file owner!
+
+ return false;
+ }
+
+ /**
+ * Does the user have delete permission on the share
+ *
+ * @param \OCP\Share\IShare $share the share to check
+ * @return boolean
+ */
+ protected function canDeleteShare(\OCP\Share\IShare $share): bool {
+ // A file with permissions 0 can't be accessed by us. So Don't show it
+ if ($share->getPermissions() === 0) {
+ return false;
+ }
+
+ // if the user is the recipient, i can unshare
+ // the share with self
+ if ($share->getShareType() === Share::SHARE_TYPE_USER &&
+ $share->getSharedWith() === $this->currentUser
+ ) {
+ return true;
+ }
+
+ // The owner of the file and the creator of the share
+ // can always delete the share
+ if ($share->getShareOwner() === $this->currentUser ||
+ $share->getSharedBy() === $this->currentUser
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Does the user have delete permission on the share
+ * This differs from the canDeleteShare function as it only
+ * remove the share for the current user. It does NOT
+ * completely delete the share but only the mount point.
+ * It can then be restored from the deleted shares section.
+ *
+ * @param \OCP\Share\IShare $share the share to check
+ * @return boolean
+ *
+ * @suppress PhanUndeclaredClassMethod
+ */
+ protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool {
+ if ($share->getShareType() !== Share::SHARE_TYPE_GROUP &&
+ $share->getShareType() !== Share::SHARE_TYPE_ROOM
+ ) {
+ return false;
+ }
+
+ if ($share->getShareOwner() === $this->currentUser ||
+ $share->getSharedBy() === $this->currentUser
+ ) {
+ // Delete the whole share, not just for self
+ return false;
+ }
+
+ // If in the recipient group, you can delete the share from self
+ if ($share->getShareType() === Share::SHARE_TYPE_GROUP) {
+ $sharedWith = $this->groupManager->get($share->getSharedWith());
+ $user = $this->userManager->get($this->currentUser);
+ if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
+ return true;
+ }
+ }
+
+ if ($share->getShareType() === Share::SHARE_TYPE_ROOM) {
+ try {
+ return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
+ } catch (QueryException $e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Make sure that the passed date is valid ISO 8601
* So YYYY-MM-DD
* If not throw an exception
@@ -1201,4 +1323,5 @@ class ShareAPIController extends OCSController {
return false;
}
+
}
diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
index 3d4dee5c64b..5a84897fe91 100644
--- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
@@ -213,19 +213,20 @@ class ShareAPIControllerTest extends TestCase {
/**
* @expectedException \OCP\AppFramework\OCS\OCSNotFoundException
- * @expectedExceptionMessage could not delete share
+ * @expectedExceptionMessage Could not delete share
*/
public function testDeleteShareLocked() {
$node = $this->getMockBuilder(File::class)->getMock();
$share = $this->newShare();
- $share->setSharedBy($this->currentUser)
- ->setNode($node);
+ $share->setNode($node);
+
$this->shareManager
->expects($this->once())
->method('getShareById')
->with('ocinternal:42')
->willReturn($share);
+
$this->shareManager
->expects($this->never())
->method('deleteShare')
@@ -235,6 +236,223 @@ class ShareAPIControllerTest extends TestCase {
->method('lock')
->with(\OCP\Lock\ILockingProvider::LOCK_SHARED)
->will($this->throwException(new LockedException('mypath')));
+
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteFromSelf', [$share]));
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
+
+ $this->ocs->deleteShare(42);
+ }
+
+ /**
+ * You can always remove a share that was shared with you
+ */
+ public function testDeleteShareWithMe() {
+ $node = $this->getMockBuilder(File::class)->getMock();
+
+ $share = $this->newShare();
+ $share->setSharedWith($this->currentUser)
+ ->setShareType(\OCP\Share::SHARE_TYPE_USER)
+ ->setNode($node);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareById')
+ ->with('ocinternal:42')
+ ->willReturn($share);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('deleteShare')
+ ->with($share);
+
+ $node->expects($this->once())
+ ->method('lock')
+ ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteFromSelf', [$share]));
+ $this->assertTrue($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
+
+ $this->ocs->deleteShare(42);
+ }
+
+ /**
+ * You can always delete a share you own
+ */
+ public function testDeleteShareOwner() {
+ $node = $this->getMockBuilder(File::class)->getMock();
+
+ $share = $this->newShare();
+ $share->setSharedBy($this->currentUser)
+ ->setNode($node);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareById')
+ ->with('ocinternal:42')
+ ->willReturn($share);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('deleteShare')
+ ->with($share);
+
+ $node->expects($this->once())
+ ->method('lock')
+ ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteFromSelf', [$share]));
+ $this->assertTrue($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
+
+ $this->ocs->deleteShare(42);
+ }
+
+ /**
+ * You can always delete a share when you own
+ * the file path it belong to
+ */
+ public function testDeleteShareFileOwner() {
+ $node = $this->getMockBuilder(File::class)->getMock();
+
+ $share = $this->newShare();
+ $share->setShareOwner($this->currentUser)
+ ->setNode($node);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareById')
+ ->with('ocinternal:42')
+ ->willReturn($share);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('deleteShare')
+ ->with($share);
+
+ $node->expects($this->once())
+ ->method('lock')
+ ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteFromSelf', [$share]));
+ $this->assertTrue($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
+
+ $this->ocs->deleteShare(42);
+ }
+
+ /**
+ * You can remove (the mountpoint, not the share)
+ * a share if you're in the group the share is shared with
+ */
+ public function testDeleteSharedWithMyGroup() {
+ $node = $this->getMockBuilder(File::class)->getMock();
+
+ $share = $this->newShare();
+ $share->setShareType(\OCP\Share::SHARE_TYPE_GROUP)
+ ->setSharedWith('group')
+ ->setNode($node);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareById')
+ ->with('ocinternal:42')
+ ->willReturn($share);
+
+ // canDeleteShareFromSelf
+ $user = $this->createMock(IUser::class);
+ $group = $this->getMockBuilder('OCP\IGroup')->getMock();
+ $this->groupManager
+ ->method('get')
+ ->with('group')
+ ->willReturn($group);
+ $this->userManager
+ ->method('get')
+ ->with($this->currentUser)
+ ->willReturn($user);
+ $group->method('inGroup')
+ ->with($user)
+ ->willReturn(true);
+
+ $node->expects($this->once())
+ ->method('lock')
+ ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
+ $userFolder->method('getById')
+ ->with($share->getNodeId())
+ ->willReturn([$share->getNode()]);
+
+ $this->shareManager->expects($this->once())
+ ->method('deleteFromSelf')
+ ->with($share, $this->currentUser);
+
+ $this->shareManager->expects($this->never())
+ ->method('deleteShare');
+
+ $this->assertTrue($this->invokePrivate($this->ocs, 'canDeleteShareFromSelf', [$share]));
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
+
+ $this->ocs->deleteShare(42);
+ }
+
+ /**
+ * You cannot remove a share if you're not
+ * in the group the share is shared with
+ * @expectedException \OCP\AppFramework\OCS\OCSNotFoundException
+ * @expectedExceptionMessage Wrong share ID, share doesn't exist
+ */
+ public function testDeleteSharedWithGroupIDontBelongTo() {
+ $node = $this->getMockBuilder(File::class)->getMock();
+
+ $share = $this->newShare();
+ $share->setShareType(\OCP\Share::SHARE_TYPE_GROUP)
+ ->setSharedWith('group')
+ ->setNode($node);
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareById')
+ ->with('ocinternal:42')
+ ->willReturn($share);
+
+ // canDeleteShareFromSelf
+ $user = $this->createMock(IUser::class);
+ $group = $this->getMockBuilder('OCP\IGroup')->getMock();
+ $this->groupManager
+ ->method('get')
+ ->with('group')
+ ->willReturn($group);
+ $this->userManager
+ ->method('get')
+ ->with($this->currentUser)
+ ->willReturn($user);
+ $group->method('inGroup')
+ ->with($user)
+ ->willReturn(false);
+
+ $node->expects($this->once())
+ ->method('lock')
+ ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
+ $userFolder->method('getById')
+ ->with($share->getNodeId())
+ ->willReturn([$share->getNode()]);
+
+ $this->shareManager->expects($this->never())
+ ->method('deleteFromSelf');
+
+ $this->shareManager->expects($this->never())
+ ->method('deleteShare');
+
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteShareFromSelf', [$share]));
+ $this->assertFalse($this->invokePrivate($this->ocs, 'canDeleteShare', [$share]));
$this->ocs->deleteShare(42);
}
@@ -558,6 +776,11 @@ class ShareAPIControllerTest extends TestCase {
->with('ocinternal:42', 'currentUser')
->willReturn($share);
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
$this->ocs->getShare(42);
}
@@ -575,6 +798,27 @@ class ShareAPIControllerTest extends TestCase {
$share->method('getSharedWith')->willReturn($this->currentUser);
$this->assertTrue($this->invokePrivate($this->ocs, 'canAccessShare', [$share]));
+ $file = $this->getMockBuilder(File::class)->getMock();
+
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
+ $userFolder->method('getById')
+ ->with($share->getNodeId())
+ ->willReturn([$file]);
+
+ $file->method('getPermissions')
+ ->will($this->onConsecutiveCalls(\OCP\Constants::PERMISSION_SHARE, \OCP\Constants::PERMISSION_READ));
+
+ // getPermissions -> share
+ $share = $this->getMockBuilder(IShare::class)->getMock();
+ $share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_USER);
+ $share->method('getSharedWith')->willReturn($this->getMockBuilder(IUser::class)->getMock());
+ $this->assertTrue($this->invokePrivate($this->ocs, 'canAccessShare', [$share]));
+
+ // getPermissions -> read
$share = $this->getMockBuilder(IShare::class)->getMock();
$share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_USER);
$share->method('getSharedWith')->willReturn($this->getMockBuilder(IUser::class)->getMock());
@@ -652,6 +896,15 @@ class ShareAPIControllerTest extends TestCase {
* @param bool canAccessShareByHelper
*/
public function testCanAccessRoomShare(bool $expected, \OCP\Share\IShare $share, bool $helperAvailable, bool $canAccessShareByHelper) {
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
+ $userFolder->method('getById')
+ ->with($share->getNodeId())
+ ->willReturn([$share->getNode()]);
+
if (!$helperAvailable) {
$this->appManager->method('isEnabledForUser')
->with('spreed')
@@ -1527,6 +1780,15 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);
+ $userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->currentUser)
+ ->willReturn($userFolder);
+
+ $userFolder->method('getById')
+ ->with($share->getNodeId())
+ ->willReturn([$share->getNode()]);
+
$this->ocs->updateShare(42);
}