From a6600e95dc73010822793f6c4aebad9e6c13e07b Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Thu, 21 Jan 2016 14:31:09 +0100 Subject: [PATCH] [Share 2.0] Add deleteFromSelf method This allows recipient to delete a share. For user shares this is the same as deleting (at least for now). But for group shares this means creating a new share with type 2. With permissions set to 0. --- lib/private/share20/defaultshareprovider.php | 79 +++++ lib/private/share20/ishareprovider.php | 9 + lib/private/share20/manager.php | 16 + .../lib/share20/defaultshareprovidertest.php | 302 +++++++++++++++++- 4 files changed, 404 insertions(+), 2 deletions(-) diff --git a/lib/private/share20/defaultshareprovider.php b/lib/private/share20/defaultshareprovider.php index 5d768a4bc4b..8c193c437d3 100644 --- a/lib/private/share20/defaultshareprovider.php +++ b/lib/private/share20/defaultshareprovider.php @@ -21,6 +21,7 @@ namespace OC\Share20; use OC\Share20\Exception\InvalidShare; +use OC\Share20\Exception\ProviderException; use OC\Share20\Exception\ShareNotFound; use OC\Share20\Exception\BackendError; use OCP\Files\NotFoundException; @@ -241,6 +242,84 @@ class DefaultShareProvider implements IShareProvider { } } + /** + * Unshare a share from the recipient. If this is a group share + * this means we need a special entry in the share db. + * + * @param IShare $share + * @param IUser $recipient + * @throws BackendError + * @throws ProviderException + */ + public function deleteFromSelf(IShare $share, IUser $recipient) { + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + + /** @var IGroup $group */ + $group = $share->getSharedWith(); + + if (!$group->inGroup($recipient)) { + throw new ProviderException('Recipient not in receiving group'); + } + + // Try to fetch user specific share + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient->getUID()))) + ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->execute(); + + $data = $stmt->fetch(); + + /* + * Check if there already is a user specific group share. + * If there is update it (if required). + */ + if ($data === false) { + $qb = $this->dbConn->getQueryBuilder(); + + $type = $share->getPath() instanceof \OCP\Files\File ? 'file' : 'folder'; + + //Insert new share + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP), + 'share_with' => $qb->createNamedParameter($recipient->getUID()), + 'uid_owner' => $qb->createNamedParameter($share->getShareOwner()->getUID()), + 'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()->getUID()), + 'parent' => $qb->createNamedParameter($share->getId()), + 'item_type' => $qb->createNamedParameter($type), + 'item_source' => $qb->createNamedParameter($share->getPath()->getId()), + 'file_source' => $qb->createNamedParameter($share->getPath()->getId()), + 'file_target' => $qb->createNamedParameter($share->getTarget()), + 'permissions' => $qb->createNamedParameter(0), + 'stime' => $qb->createNamedParameter($share->getSharetime()), + ])->execute(); + + } else if ($data['permissions'] !== 0) { + + // Update existing usergroup share + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('permissions', $qb->createNamedParameter(0)) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id']))) + ->execute(); + } + + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + + if ($share->getSharedWith() !== $recipient) { + throw new ProviderException('Recipient does not match'); + } + + // We can just delete user and link shares + $this->delete($share); + } else { + throw new ProviderException('Invalid shareType'); + } + } + /** * Get all shares by the given user. Sharetype and path can be used to filter. * diff --git a/lib/private/share20/ishareprovider.php b/lib/private/share20/ishareprovider.php index 36d0f10c7f1..17ee4abb9a8 100644 --- a/lib/private/share20/ishareprovider.php +++ b/lib/private/share20/ishareprovider.php @@ -57,6 +57,15 @@ interface IShareProvider { */ public function delete(IShare $share); + /** + * Unshare a file from self as recipient. + * This may require special handling. + * + * @param IShare $share + * @param IUser $recipient + */ + public function deleteFromSelf(IShare $share, IUser $recipient); + /** * Get all shares by the given user * diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 3935307b977..ea6463c745c 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -606,6 +606,22 @@ class Manager { } + /** + * Unshare a file as the recipient. + * This can be different from a regular delete for example when one of + * the users in a groups deletes that share. But the provider should + * handle this. + * + * @param IShare $share + * @param IUser $recipient + */ + public function deleteFromSelf(IShare $share, IUser $recipient) { + list($providerId, $id) = $this->splitFullId($share->getId()); + $provider = $this->factory->getProvider($providerId); + + $provider->deleteFromSelf($share, $recipient); + } + /** * Get shares shared by (initiated) by the provided user. * diff --git a/tests/lib/share20/defaultshareprovidertest.php b/tests/lib/share20/defaultshareprovidertest.php index 812c6ecc27e..574b1481c95 100644 --- a/tests/lib/share20/defaultshareprovidertest.php +++ b/tests/lib/share20/defaultshareprovidertest.php @@ -20,6 +20,7 @@ */ namespace Test\Share20; +use OC\Share20\Exception\ProviderException; use OCP\IDBConnection; use OCP\IUserManager; use OCP\IGroupManager; @@ -1061,7 +1062,7 @@ class DefaultShareProviderTest extends \Test\TestCase { 'share_with' => $qb->expr()->literal('sharedWith'), 'uid_owner' => $qb->expr()->literal('shareOwner'), 'uid_initiator' => $qb->expr()->literal('shareOwner'), - 'item_type' => $qb->expr()->literal('file'), + 'item_type' => $qb->expr()->literal('file'), 'file_source' => $qb->expr()->literal(42), 'file_target' => $qb->expr()->literal('myTarget'), 'permissions' => $qb->expr()->literal(13), @@ -1076,7 +1077,7 @@ class DefaultShareProviderTest extends \Test\TestCase { 'share_with' => $qb->expr()->literal('sharedWith'), 'uid_owner' => $qb->expr()->literal('shareOwner'), 'uid_initiator' => $qb->expr()->literal('sharedBy'), - 'item_type' => $qb->expr()->literal('file'), + 'item_type' => $qb->expr()->literal('file'), 'file_source' => $qb->expr()->literal(42), 'file_target' => $qb->expr()->literal('userTarget'), 'permissions' => $qb->expr()->literal(0), @@ -1123,4 +1124,301 @@ class DefaultShareProviderTest extends \Test\TestCase { $this->assertEquals(0, $share->getPermissions()); $this->assertEquals('userTarget', $share->getTarget()); } + + public function testDeleteFromSelfGroupNoCustomShare() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_GROUP), + 'share_with' => $qb->expr()->literal('group'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2) + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $user2 = $this->getMock('\OCP\IUser'); + $user2->method('getUID')->willReturn('user2'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ['user2', $user2], + ])); + + $group = $this->getMock('\OCP\IGroup'); + $group->method('getGID')->willReturn('group'); + $group->method('inGroup')->with($user2)->willReturn(true); + $this->groupManager->method('get')->with('group')->willReturn($group); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user2); + + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(2))) + ->execute(); + + $shares = $stmt->fetchAll(); + $stmt->closeCursor(); + + $this->assertCount(1, $shares); + $share2 = $shares[0]; + $this->assertEquals($id, $share2['parent']); + $this->assertEquals(0, $share2['permissions']); + $this->assertEquals('user2', $share2['share_with']); + } + + public function testDeleteFromSelfGroupAlreadyCustomShare() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_GROUP), + 'share_with' => $qb->expr()->literal('group'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2) + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(2), + 'share_with' => $qb->expr()->literal('user2'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2), + 'parent' => $qb->expr()->literal($id), + ])->execute(); + $this->assertEquals(1, $stmt); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $user2 = $this->getMock('\OCP\IUser'); + $user2->method('getUID')->willReturn('user2'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ['user2', $user2], + ])); + + $group = $this->getMock('\OCP\IGroup'); + $group->method('getGID')->willReturn('group'); + $group->method('inGroup')->with($user2)->willReturn(true); + $this->groupManager->method('get')->with('group')->willReturn($group); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user2); + + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(2))) + ->execute(); + + $shares = $stmt->fetchAll(); + $stmt->closeCursor(); + + $this->assertCount(1, $shares); + $share2 = $shares[0]; + $this->assertEquals($id, $share2['parent']); + $this->assertEquals(0, $share2['permissions']); + $this->assertEquals('user2', $share2['share_with']); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage Recipient not in receiving group + */ + public function testDeleteFromSelfGroupUserNotInGroup() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_GROUP), + 'share_with' => $qb->expr()->literal('group'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2) + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $user2 = $this->getMock('\OCP\IUser'); + $user2->method('getUID')->willReturn('user2'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ['user2', $user2], + ])); + + $group = $this->getMock('\OCP\IGroup'); + $group->method('getGID')->willReturn('group'); + $group->method('inGroup')->with($user2)->willReturn(false); + $this->groupManager->method('get')->with('group')->willReturn($group); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user2); + } + + public function testDeleteFromSelfUser() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_USER), + 'share_with' => $qb->expr()->literal('user2'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2) + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $user2 = $this->getMock('\OCP\IUser'); + $user2->method('getUID')->willReturn('user2'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ['user2', $user2], + ])); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user2); + + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->execute(); + + $shares = $stmt->fetchAll(); + $stmt->closeCursor(); + + $this->assertCount(0, $shares); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage Recipient does not match + */ + public function testDeleteFromSelfUserNotRecipient() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_USER), + 'share_with' => $qb->expr()->literal('user2'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2) + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $user2 = $this->getMock('\OCP\IUser'); + $user2->method('getUID')->willReturn('user2'); + $user3 = $this->getMock('\OCP\IUser'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ['user2', $user2], + ])); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user3); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage Invalid shareType + */ + public function testDeleteFromSelfLink() { + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_LINK), + 'uid_owner' => $qb->expr()->literal('user1'), + 'uid_initiator' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('file'), + 'file_source' => $qb->expr()->literal(1), + 'file_target' => $qb->expr()->literal('myTarget1'), + 'permissions' => $qb->expr()->literal(2), + 'token' => $qb->expr()->literal('token'), + ])->execute(); + $this->assertEquals(1, $stmt); + $id = $qb->getLastInsertId(); + + $user1 = $this->getMock('\OCP\IUser'); + $user1->method('getUID')->willReturn('user1'); + $this->userManager->method('get')->will($this->returnValueMap([ + ['user1', $user1], + ])); + + $file = $this->getMock('\OCP\Files\File'); + $file->method('getId')->willReturn(1); + + $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); + $this->rootFolder->method('getById')->with(1)->willReturn([$file]); + + $share = $this->provider->getShareById($id); + + $this->provider->deleteFromSelf($share, $user1); + } } -- 2.39.5