Commit 58d43f1e authored by Vincent Petry's avatar Vincent Petry Committed by GitHub
Browse files

Skip null groups in group manager (#26871) (#26956)

* Skip null groups in group manager (#26871)

* Skip null groups in group manager

* Also skip null groups in group manager's search function

* Add more group null checks in sharing code

* Add unit tests for null group safety in group manager

* Add unit tests for sharing code null group checks

* Added tests for null groups handling in sharing code

* Ignore moveShare optional repair in mount provider

In some cases, data is inconsistent in the oc_share table due to legacy
data. The mount provider might attempt to make it consistent but if the
target group does not exist any more it cannot work. In such case we
simply ignore the exception as it is not critical. Keeping the
exception would break user accounts as they would be unable to use
their filesystem.

* Adjust null group handing + tests

* Fix new group manager tests
parent b3462fee
......@@ -734,7 +734,7 @@ class Share20OCS {
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
$sharedWith = $this->groupManager->get($share->getSharedWith());
if ($sharedWith->inGroup($this->currentUser)) {
if (!is_null($sharedWith) && $sharedWith->inGroup($this->currentUser)) {
return true;
}
}
......
......@@ -169,7 +169,23 @@ class MountProvider implements IMountProvider {
if ($share->getTarget() !== $superShare->getTarget()) {
// adjust target, for database consistency
$share->setTarget($superShare->getTarget());
$this->shareManager->moveShare($share, $user->getUID());
try {
$this->shareManager->moveShare($share, $user->getUID());
} catch (\InvalidArgumentException $e) {
// ignore as it is not important and we don't want to
// block FS setup
// the subsequent code anyway only uses the target of the
// super share
// such issue can usually happen when dealing with
// null groups which usually appear with group backend
// caching inconsistencies
\OC::$server->getLogger()->debug(
'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
['app' => 'files_sharing']
);
}
}
}
......
......@@ -523,14 +523,19 @@ class Share20OCSTest extends \Test\TestCase {
$this->groupManager->method('get')->will($this->returnValueMap([
['group', $group],
['group2', $group2],
['groupnull', null],
]));
$this->assertTrue($this->invokePrivate($this->ocs, 'canAccessShare', [$share]));
$share = $this->createMock('OCP\Share\IShare');
$share->method('getShareType')->willReturn(Share::SHARE_TYPE_GROUP);
$share->method('getSharedWith')->willReturn('group2');
$this->assertFalse($this->invokePrivate($this->ocs, 'canAccessShare', [$share]));
$this->groupManager->method('get')->with('group2')->willReturn($group);
// null group
$share = $this->getMock('OCP\Share\IShare');
$share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_GROUP);
$share->method('getSharedWith')->willReturn('groupnull');
$this->assertFalse($this->invokePrivate($this->ocs, 'canAccessShare', [$share]));
$share = $this->createMock('OCP\Share\IShare');
......
......@@ -268,6 +268,20 @@ class MountProviderTest extends \Test\TestCase {
['1', 100, 'user2', '/share2-renamed', 31],
],
],
// #9: share as outsider with "nullgroup" and "user1" where recipient renamed in between
[
[
[2, 100, 'user2', '/share2', 31],
],
[
[1, 100, 'nullgroup', '/share2-renamed', 31],
],
[
// use target of least recent share
['1', 100, 'nullgroup', '/share2-renamed', 31],
],
true
],
];
}
......@@ -283,7 +297,7 @@ class MountProviderTest extends \Test\TestCase {
* @param array $groupShares array of group share specs
* @param array $expectedShares array of expected supershare specs
*/
public function testMergeShares($userShares, $groupShares, $expectedShares) {
public function testMergeShares($userShares, $groupShares, $expectedShares, $moveFails = false) {
$rootFolder = $this->createMock('\OCP\Files\IRootFolder');
$userManager = $this->createMock('\OCP\IUserManager');
......@@ -312,6 +326,12 @@ class MountProviderTest extends \Test\TestCase {
return new \OC\Share20\Share($rootFolder, $userManager);
}));
if ($moveFails) {
$this->shareManager->expects($this->any())
->method('moveShare')
->will($this->throwException(new \InvalidArgumentException()));
}
$mounts = $this->provider->getMountsForUser($this->user, $this->loader);
$this->assertCount(count($expectedShares), $mounts);
......
......@@ -222,7 +222,12 @@ class Manager extends PublicEmitter implements IGroupManager {
foreach ($this->backends as $backend) {
$groupIds = $backend->getGroups($search, $limit, $offset);
foreach ($groupIds as $groupId) {
$groups[$groupId] = $this->get($groupId);
$aGroup = $this->get($groupId);
if (!is_null($aGroup)) {
$groups[$groupId] = $aGroup;
} else {
\OC::$server->getLogger()->debug('Group "' . $groupId . '" was returned by search but not found through direct access', array('app' => 'core'));
}
}
if (!is_null($limit) and $limit <= 0) {
return array_values($groups);
......@@ -255,7 +260,12 @@ class Manager extends PublicEmitter implements IGroupManager {
$groupIds = $backend->getUserGroups($uid);
if (is_array($groupIds)) {
foreach ($groupIds as $groupId) {
$groups[$groupId] = $this->get($groupId);
$aGroup = $this->get($groupId);
if (!is_null($aGroup)) {
$groups[$groupId] = $aGroup;
} else {
\OC::$server->getLogger()->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', array('app' => 'core'));
}
}
}
}
......
......@@ -325,6 +325,10 @@ class DefaultShareProvider implements IShareProvider {
$group = $this->groupManager->get($share->getSharedWith());
$user = $this->userManager->get($recipient);
if (is_null($group)) {
throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
}
if (!$group->inGroup($user)) {
throw new ProviderException('Recipient not in receiving group');
}
......
......@@ -378,10 +378,12 @@ class Manager implements IManager {
// The share is already shared with this user via a group share
if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
$group = $this->groupManager->get($existingShare->getSharedWith());
$user = $this->userManager->get($share->getSharedWith());
if (!is_null($group)) {
$user = $this->userManager->get($share->getSharedWith());
if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
throw new \Exception('Path already shared with this user');
if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
throw new \Exception('Path already shared with this user');
}
}
}
}
......@@ -403,7 +405,7 @@ class Manager implements IManager {
if ($this->shareWithGroupMembersOnly()) {
$sharedBy = $this->userManager->get($share->getSharedBy());
$sharedWith = $this->groupManager->get($share->getSharedWith());
if (!$sharedWith->inGroup($sharedBy)) {
if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
throw new \Exception('Only sharing within your own groups is allowed');
}
}
......@@ -858,6 +860,9 @@ class Manager implements IManager {
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
$sharedWith = $this->groupManager->get($share->getSharedWith());
if (is_null($sharedWith)) {
throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
}
$recipient = $this->userManager->get($recipientId);
if (!$sharedWith->inGroup($recipient)) {
throw new \InvalidArgumentException('Invalid recipient');
......
......@@ -332,6 +332,32 @@ class ManagerTest extends \Test\TestCase {
$this->assertEquals('group12', $group12->getGID());
}
public function testSearchResultExistsButGroupDoesNot() {
/**
* @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend
*/
$backend = $this->createMock('\OC\Group\Database');
$backend->expects($this->once())
->method('getGroups')
->with('1')
->will($this->returnValue(['group1']));
$backend->expects($this->once())
->method('groupExists')
->with('group1')
->will($this->returnValue(false));
/**
* @var \OC\User\Manager $userManager
*/
$userManager = $this->createMock('\OC\User\Manager');
$manager = new \OC\Group\Manager($userManager);
$manager->addBackend($backend);
$groups = $manager->search('1');
$this->assertEmpty($groups);
}
public function testGetUserGroups() {
/**
* @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend
......@@ -386,6 +412,36 @@ class ManagerTest extends \Test\TestCase {
}
}
public function testGetUserGroupsWithDeletedGroup() {
/**
* @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend
*/
$backend = $this->createMock('\OC\Group\Database');
$backend->expects($this->once())
->method('getUserGroups')
->with('user1')
->will($this->returnValue(array('group1')));
$backend->expects($this->any())
->method('groupExists')
->with('group1')
->will($this->returnValue(false));
/**
* @var \OC\User\Manager $userManager
*/
$userManager = $this->createMock('\OC\User\Manager');
$userBackend = $this->createMock('\OC_User_Backend');
$manager = new \OC\Group\Manager($userManager);
$manager->addBackend($backend);
/** @var \OC\User\User $user */
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user1');
$groups = $manager->getUserGroups($user);
$this->assertEmpty($groups);
}
public function testInGroup() {
/**
* @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend
......
......@@ -1524,6 +1524,48 @@ class DefaultShareProviderTest extends TestCase {
$this->provider->deleteFromSelf($share, 'user2');
}
/**
* @expectedException \OC\Share20\Exception\ProviderException
* @expectedExceptionMessage Group "group" does not exist
*/
public function testDeleteFromSelfGroupDoesNotExist() {
$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],
]));
$this->groupManager->method('get')->with('group')->willReturn(null);
$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')
......
......@@ -1123,6 +1123,39 @@ class ManagerTest extends \Test\TestCase {
$this->invokePrivate($this->manager, 'userCreateChecks', [$share]);
}
public function testUserCreateChecksIdenticalPathSharedViaDeletedGroup() {
$share = $this->manager->newShare();
$sharedWith = $this->getMock('\OCP\IUser');
$sharedWith->method('getUID')->willReturn('sharedWith');
$this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
$path = $this->getMock('\OCP\Files\Node');
$share->setSharedWith('sharedWith')
->setNode($path)
->setShareOwner('shareOwner')
->setProviderId('foo')
->setId('bar');
$share2 = $this->manager->newShare();
$share2->setShareType(\OCP\Share::SHARE_TYPE_GROUP)
->setShareOwner('shareOwner2')
->setProviderId('foo')
->setId('baz')
->setSharedWith('group');
$this->groupManager->method('get')->with('group')->willReturn(null);
$this->defaultProvider
->method('getSharesByPath')
->with($path)
->willReturn([$share2]);
$this->assertNull($this->invokePrivate($this->manager, 'userCreateChecks', [$share]));
}
public function testUserCreateChecksIdenticalPathNotSharedWithUser() {
$share = $this->manager->newShare();
$sharedWith = $this->createMock('\OCP\IUser');
......@@ -1200,6 +1233,29 @@ class ManagerTest extends \Test\TestCase {
$this->invokePrivate($this->manager, 'groupCreateChecks', [$share]);
}
/**
* @expectedException Exception
* @expectedExceptionMessage Only sharing within your own groups is allowed
*/
public function testGroupCreateChecksShareWithGroupMembersOnlyNullGroup() {
$share = $this->manager->newShare();
$user = $this->getMock('\OCP\IUser');
$share->setSharedBy('user')->setSharedWith('group');
$this->groupManager->method('get')->with('group')->willReturn(null);
$this->userManager->method('get')->with('user')->willReturn($user);
$this->config
->method('getAppValue')
->will($this->returnValueMap([
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
]));
$this->assertNull($this->invokePrivate($this->manager, 'groupCreateChecks', [$share]));
}
public function testGroupCreateChecksShareWithGroupMembersOnlyInGroup() {
$share = $this->manager->newShare();
......@@ -2548,6 +2604,23 @@ class ManagerTest extends \Test\TestCase {
$this->manager->moveShare($share, 'recipient');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Group "shareWith" does not exist
*/
public function testMoveShareGroupNull() {
$share = $this->manager->newShare();
$share->setShareType(\OCP\Share::SHARE_TYPE_GROUP);
$share->setSharedWith('shareWith');
$recipient = $this->getMock('\OCP\IUser');
$this->groupManager->method('get')->with('shareWith')->willReturn(null);
$this->userManager->method('get')->with('recipient')->willReturn($recipient);
$this->manager->moveShare($share, 'recipient');
}
public function testMoveShareGroup() {
$share = $this->manager->newShare();
$share->setShareType(\OCP\Share::SHARE_TYPE_GROUP)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment