From cb3faad5b5631a2f020872545c53a85d9d5b0692 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 15 Sep 2023 18:49:30 +0200 Subject: [PATCH 1/5] fix(ldap): store last known user groups - for LDAP user life cycle management Signed-off-by: Arthur Schiwon --- apps/user_ldap/lib/Connection.php | 4 ++++ apps/user_ldap/lib/Group_LDAP.php | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index b47e51fdf70f4..14d3111f1d30a 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -298,6 +298,10 @@ public function getFromCache($key) { return json_decode(base64_decode($this->cache->get($key) ?? ''), true); } + public function getConfigPrefix(): string { + return $this->configPrefix; + } + /** * @param string $key * @param mixed $value diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index b3ff63d3b5c0f..9afad6ad2ff21 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -46,12 +46,16 @@ use Exception; use OC\ServerNotAvailableException; +use OCA\User_LDAP\User\OfflineUser; use OCP\Cache\CappedMemoryCache; use OCP\GroupInterface; use OCP\Group\Backend\ABackend; use OCP\Group\Backend\IDeleteGroupBackend; use OCP\Group\Backend\IGetDisplayNameBackend; +use OCP\IConfig; +use OCP\Server; use Psr\Log\LoggerInterface; +use function json_decode; class Group_LDAP extends ABackend implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend, IDeleteGroupBackend { protected bool $enabled = false; @@ -83,7 +87,7 @@ public function __construct(Access $access, GroupPluginManager $groupPluginManag $this->cachedGroupsByMember = new CappedMemoryCache(); $this->cachedNestedGroups = new CappedMemoryCache(); $this->groupPluginManager = $groupPluginManager; - $this->logger = \OCP\Server::get(LoggerInterface::class); + $this->logger = Server::get(LoggerInterface::class); $this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc); } @@ -664,15 +668,28 @@ public function getUserPrimaryGroup(string $dn) { * @throws Exception * @throws ServerNotAvailableException */ - public function getUserGroups($uid) { + public function getUserGroups($uid): array { if (!$this->enabled) { return []; } + $ncUid = $uid; + $cacheKey = 'getUserGroups' . $uid; $userGroups = $this->access->connection->getFromCache($cacheKey); if (!is_null($userGroups)) { return $userGroups; } + + $user = $this->access->userManager->get($uid); + if ($user instanceof OfflineUser) { + // We load known group memberships from configuration for remnants, + // because LDAP server does not contain them anymore + /** @var IConfig $config */ + $config = Server::get(IConfig::class); + $groupStr = $config->getUserValue($uid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), '[]'); + return json_decode($groupStr) ?? []; + } + $userDN = $this->access->username2dn($uid); if (!$userDN) { $this->access->connection->writeToCache($cacheKey, []); @@ -786,6 +803,10 @@ public function getUserGroups($uid) { $groups = array_unique($groups, SORT_LOCALE_STRING); $this->access->connection->writeToCache($cacheKey, $groups); + /** @var IConfig $config */ + $config = Server::get(IConfig::class); + $groupStr = \json_encode($groups); + $config->setUserValue($ncUid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), $groupStr); return $groups; } From 9e2d9d5df9b2f03ae174c21489744385087b25a6 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Oct 2023 21:08:28 +0200 Subject: [PATCH 2/5] test(Group LDAP): add test for getting groups of OfflineUser Signed-off-by: Arthur Schiwon --- apps/user_ldap/tests/Group_LDAPTest.php | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index 2f7c0b04cdc31..27a185ee62e6b 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -37,7 +37,10 @@ use OCA\User_LDAP\ILDAPWrapper; use OCA\User_LDAP\Mapping\GroupMapping; use OCA\User_LDAP\User\Manager; +use OCA\User_LDAP\User\OfflineUser; use OCP\GroupInterface; +use OCP\IConfig; +use OCP\Server; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -49,6 +52,14 @@ * @package OCA\User_LDAP\Tests */ class Group_LDAPTest extends TestCase { + + public function tearDown(): void { + parent::tearDown(); + + $realConfig = Server::get(IConfig::class); + $realConfig->deleteUserValue('userX', 'user_ldap', 'cached-group-memberships-'); + } + public function testCountEmptySearchString() { $access = $this->getAccessMock(); $pluginManager = $this->getPluginManagerMock(); @@ -923,6 +934,40 @@ public function testGetUserGroupsMemberOfDisabled() { $groupBackend->getUserGroups('userX'); } + public function testGetUserGroupsOfflineUser() { + $access = $this->getAccessMock(); + $pluginManager = $this->getPluginManagerMock(); + + $access->connection = $this->createMock(Connection::class); + $access->connection->expects($this->any()) + ->method('__get') + ->willReturnCallback(function ($name) { + if ($name === 'useMemberOfToDetectMembership') { + return 0; + } elseif ($name === 'ldapDynamicGroupMemberURL') { + return ''; + } + return 1; + }); + + $offlineUser = $this->createMock(OfflineUser::class); + + // FIXME: should be available via CI + $realConfig = Server::get(IConfig::class); + $realConfig->setUserValue('userX', 'user_ldap', 'cached-group-memberships-', \json_encode(['groupB', 'groupF'])); + + $access->userManager->expects($this->any()) + ->method('get') + ->with('userX') + ->willReturn($offlineUser); + + $groupBackend = new GroupLDAP($access, $pluginManager); + $returnedGroups = $groupBackend->getUserGroups('userX'); + $this->assertCount(2, $returnedGroups); + $this->assertTrue(in_array('groupB', $returnedGroups)); + $this->assertTrue(in_array('groupF', $returnedGroups)); + } + public function nestedGroupsProvider(): array { return [ [true], From 039da6bd22dd38cd8e127e604975856c2509995e Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Oct 2023 21:58:32 +0200 Subject: [PATCH 3/5] refactor(tests): simplify code, reduce duplication Signed-off-by: Arthur Schiwon --- apps/user_ldap/tests/Group_LDAPTest.php | 682 +++++++++--------------- 1 file changed, 257 insertions(+), 425 deletions(-) diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index 27a185ee62e6b..e66fdd633a228 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -53,6 +53,18 @@ */ class Group_LDAPTest extends TestCase { + public function setUp(): void { + parent::setUp(); + + $this->access = $this->getAccessMock(); + $this->pluginManager = $this->createMock(GroupPluginManager::class); + } + + public function initBackend(): void { + $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager); + } + + public function tearDown(): void { parent::tearDown(); @@ -61,16 +73,14 @@ public function tearDown(): void { } public function testCountEmptySearchString() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); $groupDN = 'cn=group,dc=foo,dc=bar'; - $this->enableGroups($access); + $this->enableGroups(); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn($groupDN); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($dn) use ($groupDN) { if ($dn === $groupDN) { @@ -83,20 +93,20 @@ public function testCountEmptySearchString() { } return []; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); // for primary groups - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('countUsers') ->willReturn(2); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['displayName', 'mail']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $users = $groupBackend->countUsersInGroup('group'); + $this->initBackend(); + $users = $this->groupBackend->countUsersInGroup('group'); $this->assertSame(6, $users); } @@ -113,31 +123,23 @@ private function getAccessMock() { $accMethods = get_class_methods(Access::class); } $lw = $this->createMock(ILDAPWrapper::class); + $connector = $this->getMockBuilder(Connection::class) ->setMethods($conMethods) ->setConstructorArgs([$lw, '', null]) ->getMock(); - $access = $this->createMock(Access::class); - - $access->connection = $connector; + $this->access = $this->createMock(Access::class); - $access->userManager = $this->createMock(Manager::class); + $this->access->connection = $connector; - return $access; - } + $this->access->userManager = $this->createMock(Manager::class); - /** - * @return MockObject|GroupPluginManager - */ - private function getPluginManagerMock() { - return $this->createMock(GroupPluginManager::class); + return $this->access; } - private function enableGroups(Access $access) { - $access->connection = $this->createMock(Connection::class); - - $access->connection->expects($this->any()) + private function enableGroups() { + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { if ($name === 'ldapDynamicGroupMemberURL') { @@ -150,18 +152,15 @@ private function enableGroups(Access $access) { } public function testCountWithSearchString() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); + $this->enableGroups(); - $this->enableGroups($access); - - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=group,dc=foo,dc=bar'); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('fetchListOfUsers') ->willReturn([]); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($name) { //the search operation will call readAttribute, thus we need @@ -174,301 +173,257 @@ public function testCountWithSearchString() { } return ['u11', 'u22', 'u33', 'u34']; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('dn2username') ->willReturnCallback(function () { return 'foobar' . \OC::$server->getSecureRandom()->generate(7); }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('escapeFilterPart') ->willReturnArgument(0); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['displayName', 'mail']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $users = $groupBackend->countUsersInGroup('group', '3'); + $this->initBackend(); + $users = $this->groupBackend->countUsersInGroup('group', '3'); $this->assertSame(2, $users); } public function testCountUsersWithPlugin() { /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'countUsersInGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::COUNT_USERS) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('countUsersInGroup') ->with('gid', 'search') ->willReturn(42); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertEquals($ldap->countUsersInGroup('gid', 'search'), 42); + $this->initBackend(); + $this->assertEquals($this->groupBackend->countUsersInGroup('gid', 'search'), 42); } public function testGidNumber2NameSuccess() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2groupname') ->with('cn=foo,dc=barfoo,dc=bar') ->willReturn('MyGroup'); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->gidNumber2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->gidNumber2Name('3117', $userDN); $this->assertSame('MyGroup', $group); } public function testGidNumberID2NameNoGroup() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([]); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2groupname'); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->gidNumber2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->gidNumber2Name('3117', $userDN); $this->assertSame(false, $group); } public function testGidNumberID2NameNoName() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2groupname') ->willReturn(false); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->gidNumber2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->gidNumber2Name('3117', $userDN); $this->assertSame(false, $group); } public function testGetEntryGidNumberValue() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar'; $attr = 'gidNumber'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('readAttribute') ->with($dn, $attr) ->willReturn(['3117']); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $gid = $groupBackend->getGroupGidNumber($dn); + $this->initBackend(); + $gid = $this->groupBackend->getGroupGidNumber($dn); $this->assertSame('3117', $gid); } public function testGetEntryGidNumberNoValue() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar'; $attr = 'gidNumber'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('readAttribute') ->with($dn, $attr) ->willReturn(false); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $gid = $groupBackend->getGroupGidNumber($dn); + $this->initBackend(); + $gid = $this->groupBackend->getGroupGidNumber($dn); $this->assertSame(false, $gid); } public function testPrimaryGroupID2NameSuccessCache() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; $gid = '3117'; - $groupDN = 'cn=foo,dc=barfoo,dc=bar'; /** @var MockObject $connection */ - $connection = $access->connection; + $connection = $this->access->connection; $connection->expects($this->once()) ->method('getFromCache') ->with('primaryGroupIDtoName_' . $gid) ->willReturn('MyGroup'); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('getSID'); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('searchGroups'); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2groupname'); - $groupBackend = new GroupLDAP($access, $pluginManager); - $group = $groupBackend->primaryGroupID2Name($gid, $userDN); + $this->initBackend(); + $group = $this->groupBackend->primaryGroupID2Name($gid, $userDN); $this->assertSame('MyGroup', $group); } public function testPrimaryGroupID2NameSuccess() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('getSID') ->with($userDN) ->willReturn('S-1-5-21-249921958-728525901-1594176202'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2groupname') ->with('cn=foo,dc=barfoo,dc=bar') ->willReturn('MyGroup'); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->primaryGroupID2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN); $this->assertSame('MyGroup', $group); } public function testPrimaryGroupID2NameNoSID() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('getSID') ->with($userDN) ->willReturn(false); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('searchGroups'); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2groupname'); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->primaryGroupID2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN); $this->assertSame(false, $group); } public function testPrimaryGroupID2NameNoGroup() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('getSID') ->with($userDN) ->willReturn('S-1-5-21-249921958-728525901-1594176202'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([]); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2groupname'); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->primaryGroupID2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN); $this->assertSame(false, $group); } public function testPrimaryGroupID2NameNoName() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('getSID') ->with($userDN) ->willReturn('S-1-5-21-249921958-728525901-1594176202'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('searchGroups') ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2groupname') ->willReturn(false); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $group = $groupBackend->primaryGroupID2Name('3117', $userDN); + $this->initBackend(); + $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN); $this->assertSame(false, $group); } @@ -476,22 +431,18 @@ public function testPrimaryGroupID2NameNoName() { public function testGetEntryGroupIDValue() { //tests getEntryGroupID via getGroupPrimaryGroupID //which is basically identical to getUserPrimaryGroupIDs - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar'; $attr = 'primaryGroupToken'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('readAttribute') ->with($dn, $attr) ->willReturn(['3117']); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $gid = $groupBackend->getGroupPrimaryGroupID($dn); + $this->initBackend(); + $gid = $this->groupBackend->getGroupPrimaryGroupID($dn); $this->assertSame('3117', $gid); } @@ -499,22 +450,18 @@ public function testGetEntryGroupIDValue() { public function testGetEntryGroupIDNoValue() { //tests getEntryGroupID via getGroupPrimaryGroupID //which is basically identical to getUserPrimaryGroupIDs - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar'; $attr = 'primaryGroupToken'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('readAttribute') ->with($dn, $attr) ->willReturn(false); - $groupBackend = new GroupLDAP($access, $pluginManager); - - $gid = $groupBackend->getGroupPrimaryGroupID($dn); + $this->initBackend(); + $gid = $this->groupBackend->getGroupPrimaryGroupID($dn); $this->assertSame(false, $gid); } @@ -524,25 +471,22 @@ public function testGetEntryGroupIDNoValue() { * is hit */ public function testInGroupHitsUidGidCache() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $uid = 'someUser'; $gid = 'someGroup'; $cacheKey = 'inGroup' . $uid . ':' . $gid; - $access->connection->expects($this->once()) + $this->access->connection->expects($this->once()) ->method('getFromCache') ->with($cacheKey) ->willReturn(true); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('username2dn'); - $groupBackend = new GroupLDAP($access, $pluginManager); - $groupBackend->inGroup($uid, $gid); + $this->initBackend(); + $this->groupBackend->inGroup($uid, $gid); } public function groupWithMembersProvider() { @@ -564,15 +508,10 @@ public function groupWithMembersProvider() { * @dataProvider groupWithMembersProvider */ public function testInGroupMember(string $gid, string $groupDn, array $memberDNs) { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $access->connection = $this->createMock(Connection::class); - $uid = 'someUser'; $userDn = $memberDNs[0]; - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { switch ($name) { @@ -587,38 +526,33 @@ public function testInGroupMember(string $gid, string $groupDn, array $memberDNs return 1; } }); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->with($uid) ->willReturn($userDn); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('groupname2dn') ->willReturn($groupDn); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturn($memberDNs); - $groupBackend = new GroupLDAP($access, $pluginManager); - $this->assertTrue($groupBackend->inGroup($uid, $gid)); + $this->initBackend(); + $this->assertTrue($this->groupBackend->inGroup($uid, $gid)); } /** * @dataProvider groupWithMembersProvider */ public function testInGroupMemberNot(string $gid, string $groupDn, array $memberDNs) { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $access->connection = $this->createMock(Connection::class); - $uid = 'unelatedUser'; $userDn = 'uid=unrelatedUser,ou=unrelatedTeam,ou=unrelatedDepartment,dc=someDomain,dc=someTld'; - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { switch ($name) { @@ -633,32 +567,29 @@ public function testInGroupMemberNot(string $gid, string $groupDn, array $member return 1; } }); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->with($uid) ->willReturn($userDn); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('groupname2dn') ->willReturn($groupDn); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturn($memberDNs); - $groupBackend = new GroupLDAP($access, $pluginManager); - $this->assertFalse($groupBackend->inGroup($uid, $gid)); + $this->initBackend(); + $this->assertFalse($this->groupBackend->inGroup($uid, $gid)); } /** * @dataProvider groupWithMembersProvider */ public function testInGroupMemberUid(string $gid, string $groupDn, array $memberDNs) { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - $memberUids = []; $userRecords = []; foreach ($memberDNs as $dn) { @@ -666,13 +597,10 @@ public function testInGroupMemberUid(string $gid, string $groupDn, array $member $userRecords[] = ['dn' => [$dn]]; } - - $access->connection = $this->createMock(Connection::class); - $uid = 'someUser'; $userDn = $memberDNs[0]; - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { switch ($name) { @@ -689,47 +617,44 @@ public function testInGroupMemberUid(string $gid, string $groupDn, array $member return 1; } }); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['uid', 'mail', 'displayname']); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->with($uid) ->willReturn($userDn); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('groupname2dn') ->willReturn($groupDn); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturn($memberUids); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('fetchListOfUsers') ->willReturn($userRecords); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('combineFilterWithOr') ->willReturn('(|(pseudo=filter)(filter=pseudo))'); - $groupBackend = new GroupLDAP($access, $pluginManager); - $this->assertTrue($groupBackend->inGroup($uid, $gid)); + $this->initBackend(); + $this->assertTrue($this->groupBackend->inGroup($uid, $gid)); } public function testGetGroupsWithOffset() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('nextcloudGroupNames') ->willReturn(['group1', 'group2']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $groups = $groupBackend->getGroups('', 2, 2); + $this->initBackend(); + $groups = $this->groupBackend->getGroups('', 2, 2); $this->assertSame(2, count($groups)); } @@ -739,15 +664,12 @@ public function testGetGroupsWithOffset() { * as their primary. */ public function testUsersInGroupPrimaryMembersOnly() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($dn, $attr) { if ($attr === 'primaryGroupToken') { @@ -757,25 +679,25 @@ public function testUsersInGroupPrimaryMembersOnly() { } return []; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=foobar,dc=foo,dc=bar'); - $access->expects($this->exactly(2)) + $this->access->expects($this->exactly(2)) ->method('nextcloudUserNames') ->willReturnOnConsecutiveCalls(['lisa', 'bart', 'kira', 'brad'], ['walle', 'dino', 'xenia']); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('combineFilterWithAnd') ->willReturn('pseudo=filter'); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['displayName', 'mail']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $users = $groupBackend->usersInGroup('foobar'); + $this->initBackend(); + $users = $this->groupBackend->usersInGroup('foobar'); $this->assertSame(7, count($users)); } @@ -785,15 +707,12 @@ public function testUsersInGroupPrimaryMembersOnly() { * as their primary. */ public function testUsersInGroupPrimaryAndUnixMembers() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); + $this->enableGroups(); - $this->enableGroups($access); - - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($dn, $attr) { if ($attr === 'primaryGroupToken') { @@ -801,25 +720,25 @@ public function testUsersInGroupPrimaryAndUnixMembers() { } return []; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=foobar,dc=foo,dc=bar'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('nextcloudUserNames') ->willReturn(['lisa', 'bart', 'kira', 'brad']); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('combineFilterWithAnd') ->willReturn('pseudo=filter'); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['displayName', 'mail']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $users = $groupBackend->usersInGroup('foobar'); + $this->initBackend(); + $users = $this->groupBackend->usersInGroup('foobar'); $this->assertSame(4, count($users)); } @@ -829,16 +748,13 @@ public function testUsersInGroupPrimaryAndUnixMembers() { * as their primary. */ public function testCountUsersInGroupPrimaryMembersOnly() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('getFromCache') ->willReturn(null); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($dn, $attr) { if ($attr === 'primaryGroupToken') { @@ -846,65 +762,58 @@ public function testCountUsersInGroupPrimaryMembersOnly() { } return []; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=foobar,dc=foo,dc=bar'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('countUsers') ->willReturn(4); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['displayName', 'mail']); - $groupBackend = new GroupLDAP($access, $pluginManager); - $users = $groupBackend->countUsersInGroup('foobar'); + $this->initBackend(); + $users = $this->groupBackend->countUsersInGroup('foobar'); $this->assertSame(4, $users); } public function testGetUserGroupsMemberOf() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $this->enableGroups($access); + $this->enableGroups(); $dn = 'cn=userX,dc=foobar'; - $access->connection->hasPrimaryGroups = false; - $access->connection->hasGidNumber = false; + $this->access->connection->hasPrimaryGroups = false; + $this->access->connection->hasGidNumber = false; - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('username2dn') ->willReturn($dn); - $access->expects($this->exactly(5)) + $this->access->expects($this->exactly(5)) ->method('readAttribute') ->will($this->onConsecutiveCalls(['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar'], [], [], [], [])); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('dn2groupname') ->willReturnArgument(0); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturnArgument(0); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $groupBackend = new GroupLDAP($access, $pluginManager); - $groups = $groupBackend->getUserGroups('userX'); + $this->initBackend(); + $groups = $this->groupBackend->getUserGroups('userX'); $this->assertSame(2, count($groups)); } public function testGetUserGroupsMemberOfDisabled() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $access->connection = $this->createMock(Connection::class); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { if ($name === 'useMemberOfToDetectMembership') { @@ -917,38 +826,25 @@ public function testGetUserGroupsMemberOfDisabled() { $dn = 'cn=userX,dc=foobar'; - $access->connection->hasPrimaryGroups = false; - $access->connection->hasGidNumber = false; + $this->access->connection->hasPrimaryGroups = false; + $this->access->connection->hasGidNumber = false; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->willReturn($dn); - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('readAttribute') ->with($dn, 'memberOf'); - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('nextcloudGroupNames') ->willReturn([]); - $groupBackend = new GroupLDAP($access, $pluginManager); - $groupBackend->getUserGroups('userX'); + $this->initBackend(); + $this->groupBackend->getUserGroups('userX'); } public function testGetUserGroupsOfflineUser() { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - - $access->connection = $this->createMock(Connection::class); - $access->connection->expects($this->any()) - ->method('__get') - ->willReturnCallback(function ($name) { - if ($name === 'useMemberOfToDetectMembership') { - return 0; - } elseif ($name === 'ldapDynamicGroupMemberURL') { - return ''; - } - return 1; - }); + $this->enableGroups(); $offlineUser = $this->createMock(OfflineUser::class); @@ -956,13 +852,13 @@ public function testGetUserGroupsOfflineUser() { $realConfig = Server::get(IConfig::class); $realConfig->setUserValue('userX', 'user_ldap', 'cached-group-memberships-', \json_encode(['groupB', 'groupF'])); - $access->userManager->expects($this->any()) + $this->access->userManager->expects($this->any()) ->method('get') ->with('userX') ->willReturn($offlineUser); - $groupBackend = new GroupLDAP($access, $pluginManager); - $returnedGroups = $groupBackend->getUserGroups('userX'); + $this->initBackend(); + $returnedGroups = $this->groupBackend->getUserGroups('userX'); $this->assertCount(2, $returnedGroups); $this->assertTrue(in_array('groupB', $returnedGroups)); $this->assertTrue(in_array('groupF', $returnedGroups)); @@ -979,12 +875,8 @@ public function nestedGroupsProvider(): array { * @dataProvider nestedGroupsProvider */ public function testGetGroupsByMember(bool $nestedGroups) { - $access = $this->getAccessMock(); - $pluginManager = $this->getPluginManagerMock(); - $groupFilter = '(&(objectclass=nextcloudGroup)(nextcloudEnabled=TRUE))'; - $access->connection = $this->createMock(Connection::class); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function (string $name) use ($nestedGroups, $groupFilter) { switch ($name) { @@ -1008,16 +900,16 @@ public function testGetGroupsByMember(bool $nestedGroups) { $dn = 'cn=userX,dc=foobar'; - $access->connection->hasPrimaryGroups = false; - $access->connection->hasGidNumber = false; + $this->access->connection->hasPrimaryGroups = false; + $this->access->connection->hasGidNumber = false; - $access->expects($this->exactly(2)) + $this->access->expects($this->exactly(2)) ->method('username2dn') ->willReturn($dn); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturn([]); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('combineFilterWithAnd') ->willReturnCallback(function (array $filterParts) { // ⚠ returns a pseudo-filter only, not real LDAP Filter syntax @@ -1043,11 +935,11 @@ public function testGetGroupsByMember(bool $nestedGroups) { $expectedGroups = ($nestedGroups ? [$group1, $group2, $group3] : [$group1, $group2]); $expectedGroupsNames = ($nestedGroups ? ['group1', 'group2', 'group3'] : ['group1', 'group2']); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('nextcloudGroupNames') ->with($expectedGroups) ->willReturn($expectedGroupsNames); - $access->expects($nestedGroups ? $this->atLeastOnce() : $this->once()) + $this->access->expects($nestedGroups ? $this->atLeastOnce() : $this->once()) ->method('fetchListOfGroups') ->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2, $group3, $dn) { static $firstRun = true; @@ -1066,12 +958,12 @@ public function testGetGroupsByMember(bool $nestedGroups) { return []; } }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('dn2groupname') ->willReturnCallback(function (string $dn) { return ldap_explode_dn($dn, 1)[0]; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturnCallback(function (string $gid) use ($group1, $group2, $group3) { if ($gid === $group1['cn']) { @@ -1084,76 +976,65 @@ public function testGetGroupsByMember(bool $nestedGroups) { return $group3['dn'][0]; } }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('isDNPartOfBase') ->willReturn(true); - $groupBackend = new GroupLDAP($access, $pluginManager); - $groups = $groupBackend->getUserGroups('userX'); + $this->initBackend(); + $groups = $this->groupBackend->getUserGroups('userX'); $this->assertEquals($expectedGroupsNames, $groups); - $groupsAgain = $groupBackend->getUserGroups('userX'); + $groupsAgain = $this->groupBackend->getUserGroups('userX'); $this->assertEquals($expectedGroupsNames, $groupsAgain); } public function testCreateGroupWithPlugin() { - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'createGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::CREATE_GROUP) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('createGroup') ->with('gid') ->willReturn('result'); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertEquals($ldap->createGroup('gid'), true); + $this->initBackend(); + $this->assertEquals($this->groupBackend->createGroup('gid'), true); } public function testCreateGroupFailing() { $this->expectException(\Exception::class); - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'createGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::CREATE_GROUP) ->willReturn(false); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $ldap->createGroup('gid'); + $this->initBackend(); + $this->groupBackend->createGroup('gid'); } public function testDeleteGroupWithPlugin() { - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'deleteGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::DELETE_GROUP) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('deleteGroup') ->with('gid') ->willReturn(true); @@ -1163,176 +1044,137 @@ public function testDeleteGroupWithPlugin() { ->disableOriginalConstructor() ->getMock(); - $access = $this->getAccessMock(); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('getGroupMapper') ->willReturn($mapper); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertTrue($ldap->deleteGroup('gid')); + $this->initBackend(); + $this->assertTrue($this->groupBackend->deleteGroup('gid')); } public function testDeleteGroupFailing() { $this->expectException(\Exception::class); - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'deleteGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::DELETE_GROUP) ->willReturn(false); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $ldap->deleteGroup('gid'); + $this->initBackend(); + $this->groupBackend->deleteGroup('gid'); } public function testAddToGroupWithPlugin() { - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'addToGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::ADD_TO_GROUP) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('addToGroup') ->with('uid', 'gid') ->willReturn('result'); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertEquals($ldap->addToGroup('uid', 'gid'), 'result'); + $this->initBackend(); + $this->assertEquals($this->groupBackend->addToGroup('uid', 'gid'), 'result'); } public function testAddToGroupFailing() { $this->expectException(\Exception::class); - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'addToGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::ADD_TO_GROUP) ->willReturn(false); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $ldap->addToGroup('uid', 'gid'); + $this->initBackend(); + $this->groupBackend->addToGroup('uid', 'gid'); } public function testRemoveFromGroupWithPlugin() { - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'removeFromGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::REMOVE_FROM_GROUP) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('removeFromGroup') ->with('uid', 'gid') ->willReturn('result'); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertEquals($ldap->removeFromGroup('uid', 'gid'), 'result'); + $this->initBackend(); + $this->assertEquals($this->groupBackend->removeFromGroup('uid', 'gid'), 'result'); } public function testRemoveFromGroupFailing() { $this->expectException(\Exception::class); - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'removeFromGroup']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::REMOVE_FROM_GROUP) ->willReturn(false); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $ldap->removeFromGroup('uid', 'gid'); + $this->initBackend(); + $this->groupBackend->removeFromGroup('uid', 'gid'); } public function testGetGroupDetailsWithPlugin() { /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'getGroupDetails']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::GROUP_DETAILS) ->willReturn(true); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('getGroupDetails') ->with('gid') ->willReturn('result'); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $this->assertEquals($ldap->getGroupDetails('gid'), 'result'); + $this->initBackend(); + $this->assertEquals($this->groupBackend->getGroupDetails('gid'), 'result'); } - public function testGetGroupDetailsFailing() { $this->expectException(\Exception::class); - /** @var GroupPluginManager|MockObject $pluginManager */ - $pluginManager = $this->getMockBuilder(GroupPluginManager::class) + $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class) ->setMethods(['implementsActions', 'getGroupDetails']) ->getMock(); - $pluginManager->expects($this->once()) + $this->pluginManager->expects($this->once()) ->method('implementsActions') ->with(GroupInterface::GROUP_DETAILS) ->willReturn(false); - $access = $this->getAccessMock(); - $access->connection = $this->createMock(Connection::class); - - $ldap = new GroupLDAP($access, $pluginManager); - - $ldap->getGroupDetails('gid'); + $this->initBackend(); + $this->groupBackend->getGroupDetails('gid'); } public function groupMemberProvider() { @@ -1408,8 +1250,7 @@ public function groupMemberProvider() { * @dataProvider groupMemberProvider */ public function testGroupMembers(array $expectedResult, array $groupsInfo = null) { - $access = $this->getAccessMock(); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($group) use ($groupsInfo) { if (isset($groupsInfo[$group])) { @@ -1418,8 +1259,7 @@ public function testGroupMembers(array $expectedResult, array $groupsInfo = null return []; }); - $access->connection = $this->createMock(Connection::class); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function (string $name) { if ($name === 'ldapNestedGroups') { @@ -1430,12 +1270,9 @@ public function testGroupMembers(array $expectedResult, array $groupsInfo = null return null; }); - /** @var GroupPluginManager $pluginManager */ - $pluginManager = $this->createMock(GroupPluginManager::class); - - $ldap = new GroupLDAP($access, $pluginManager); + $this->initBackend(); foreach ($expectedResult as $groupDN => $expectedMembers) { - $resultingMembers = $this->invokePrivate($ldap, '_groupMembers', [$groupDN]); + $resultingMembers = $this->invokePrivate($this->groupBackend, '_groupMembers', [$groupDN]); $this->assertEqualsCanonicalizing($expectedMembers, $resultingMembers); } @@ -1454,13 +1291,11 @@ public function displayNameProvider() { public function testGetDisplayName(string $expected, $ldapResult) { $gid = 'graphic_novelists'; - $access = $this->getAccessMock(); - $access->expects($this->atLeastOnce()) + $this->access->expects($this->atLeastOnce()) ->method('readAttribute') ->willReturn($ldapResult); - $access->connection = $this->createMock(Connection::class); - $access->connection->expects($this->any()) + $this->access->connection->expects($this->any()) ->method('__get') ->willReturnCallback(function ($name) { if ($name === 'ldapGroupMemberAssocAttr') { @@ -1473,14 +1308,11 @@ public function testGetDisplayName(string $expected, $ldapResult) { return null; }); - $access->expects($this->any()) + $this->access->expects($this->any()) ->method('groupname2dn') ->willReturn('fakedn'); - /** @var GroupPluginManager $pluginManager */ - $pluginManager = $this->createMock(GroupPluginManager::class); - - $ldap = new GroupLDAP($access, $pluginManager); - $this->assertSame($expected, $ldap->getDisplayName($gid)); + $this->initBackend(); + $this->assertSame($expected, $this->groupBackend->getDisplayName($gid)); } } From c1480aade4ebc6988824d93acdb7fe854bdc8be7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Oct 2023 22:18:39 +0200 Subject: [PATCH 4/5] refactor(LDAP): pass IConfig via constructor to Group_LDAP Signed-off-by: Arthur Schiwon --- apps/user_ldap/lib/Group_LDAP.php | 12 ++++------ apps/user_ldap/lib/Group_Proxy.php | 8 +++++-- apps/user_ldap/tests/Group_LDAPTest.php | 23 ++++++++----------- .../Lib/IntegrationTestAttributeDetection.php | 3 ++- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 9afad6ad2ff21..df54678db996b 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -74,8 +74,9 @@ class Group_LDAP extends ABackend implements GroupInterface, IGroupLDAP, IGetDis * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name */ protected string $ldapGroupMemberAssocAttr; + private IConfig $config; - public function __construct(Access $access, GroupPluginManager $groupPluginManager) { + public function __construct(Access $access, GroupPluginManager $groupPluginManager, IConfig $config) { $this->access = $access; $filter = $this->access->connection->ldapGroupFilter; $gAssoc = $this->access->connection->ldapGroupMemberAssocAttr; @@ -89,6 +90,7 @@ public function __construct(Access $access, GroupPluginManager $groupPluginManag $this->groupPluginManager = $groupPluginManager; $this->logger = Server::get(LoggerInterface::class); $this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc); + $this->config = $config; } /** @@ -684,9 +686,7 @@ public function getUserGroups($uid): array { if ($user instanceof OfflineUser) { // We load known group memberships from configuration for remnants, // because LDAP server does not contain them anymore - /** @var IConfig $config */ - $config = Server::get(IConfig::class); - $groupStr = $config->getUserValue($uid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), '[]'); + $groupStr = $this->config->getUserValue($uid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), '[]'); return json_decode($groupStr) ?? []; } @@ -803,10 +803,8 @@ public function getUserGroups($uid): array { $groups = array_unique($groups, SORT_LOCALE_STRING); $this->access->connection->writeToCache($cacheKey, $groups); - /** @var IConfig $config */ - $config = Server::get(IConfig::class); $groupStr = \json_encode($groups); - $config->setUserValue($ncUid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), $groupStr); + $this->config->setUserValue($ncUid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), $groupStr); return $groups; } diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index 114902ff9bad7..01a684662977b 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -35,6 +35,7 @@ use OCP\Group\Backend\IGroupDetailsBackend; use OCP\Group\Backend\INamedBackend; use OCP\GroupInterface; +use OCP\IConfig; class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend, IBatchMethodsBackend { private $backends = []; @@ -42,16 +43,19 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet private Helper $helper; private GroupPluginManager $groupPluginManager; private bool $isSetUp = false; + private IConfig $config; public function __construct( Helper $helper, ILDAPWrapper $ldap, AccessFactory $accessFactory, - GroupPluginManager $groupPluginManager + GroupPluginManager $groupPluginManager, + IConfig $config, ) { parent::__construct($ldap, $accessFactory); $this->helper = $helper; $this->groupPluginManager = $groupPluginManager; + $this->config = $config; } protected function setup(): void { @@ -62,7 +66,7 @@ protected function setup(): void { $serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true); foreach ($serverConfigPrefixes as $configPrefix) { $this->backends[$configPrefix] = - new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager); + new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager, $this->config); if (is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index e66fdd633a228..b4789b09c3d25 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -40,7 +40,6 @@ use OCA\User_LDAP\User\OfflineUser; use OCP\GroupInterface; use OCP\IConfig; -use OCP\Server; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -52,24 +51,21 @@ * @package OCA\User_LDAP\Tests */ class Group_LDAPTest extends TestCase { + private MockObject|Access $access; + private MockObject|GroupPluginManager $pluginManager; + private MockObject|IConfig $config; + private GroupLDAP $groupBackend; public function setUp(): void { parent::setUp(); $this->access = $this->getAccessMock(); $this->pluginManager = $this->createMock(GroupPluginManager::class); + $this->config = $this->createMock(IConfig::class); } public function initBackend(): void { - $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager); - } - - - public function tearDown(): void { - parent::tearDown(); - - $realConfig = Server::get(IConfig::class); - $realConfig->deleteUserValue('userX', 'user_ldap', 'cached-group-memberships-'); + $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager, $this->config); } public function testCountEmptySearchString() { @@ -848,9 +844,10 @@ public function testGetUserGroupsOfflineUser() { $offlineUser = $this->createMock(OfflineUser::class); - // FIXME: should be available via CI - $realConfig = Server::get(IConfig::class); - $realConfig->setUserValue('userX', 'user_ldap', 'cached-group-memberships-', \json_encode(['groupB', 'groupF'])); + $this->config->expects($this->any()) + ->method('getUserValue') + ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything()) + ->willReturn(\json_encode(['groupB', 'groupF'])); $this->access->userManager->expects($this->any()) ->method('get') diff --git a/apps/user_ldap/tests/Integration/Lib/IntegrationTestAttributeDetection.php b/apps/user_ldap/tests/Integration/Lib/IntegrationTestAttributeDetection.php index a742c0b80764b..e77b3aec6b041 100644 --- a/apps/user_ldap/tests/Integration/Lib/IntegrationTestAttributeDetection.php +++ b/apps/user_ldap/tests/Integration/Lib/IntegrationTestAttributeDetection.php @@ -31,6 +31,7 @@ use OCA\User_LDAP\User\DeletedUsersIndex; use OCA\User_LDAP\User_LDAP; use OCA\User_LDAP\UserPluginManager; +use OCP\IConfig; use Psr\Log\LoggerInterface; require_once __DIR__ . '/../Bootstrap.php'; @@ -58,7 +59,7 @@ public function init() { $userManager->clearBackends(); $userManager->registerBackend($userBackend); - $groupBackend = new Group_LDAP($this->access, \OC::$server->query(GroupPluginManager::class)); + $groupBackend = new Group_LDAP($this->access, \OC::$server->query(GroupPluginManager::class), \OC::$server->get(IConfig::class)); $groupManger = \OC::$server->getGroupManager(); $groupManger->clearBackends(); $groupManger->addBackend($groupBackend); From cce8d0a7a5113675f4778e07ef8d87ea0934482e Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 11 Oct 2023 14:06:27 +0200 Subject: [PATCH 5/5] fix(LDAP): solve race condition reading groups of disappeared LDAP user Signed-off-by: Arthur Schiwon --- apps/user_ldap/lib/Group_LDAP.php | 45 +++++++++++-- apps/user_ldap/lib/Group_Proxy.php | 6 +- apps/user_ldap/tests/Group_LDAPTest.php | 87 ++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index df54678db996b..fd2945106bc17 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -53,6 +53,7 @@ use OCP\Group\Backend\IDeleteGroupBackend; use OCP\Group\Backend\IGetDisplayNameBackend; use OCP\IConfig; +use OCP\IUserManager; use OCP\Server; use Psr\Log\LoggerInterface; use function json_decode; @@ -75,8 +76,14 @@ class Group_LDAP extends ABackend implements GroupInterface, IGroupLDAP, IGetDis */ protected string $ldapGroupMemberAssocAttr; private IConfig $config; - - public function __construct(Access $access, GroupPluginManager $groupPluginManager, IConfig $config) { + private IUserManager $ncUserManager; + + public function __construct( + Access $access, + GroupPluginManager $groupPluginManager, + IConfig $config, + IUserManager $ncUserManager + ) { $this->access = $access; $filter = $this->access->connection->ldapGroupFilter; $gAssoc = $this->access->connection->ldapGroupMemberAssocAttr; @@ -91,6 +98,7 @@ public function __construct(Access $access, GroupPluginManager $groupPluginManag $this->logger = Server::get(LoggerInterface::class); $this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc); $this->config = $config; + $this->ncUserManager = $ncUserManager; } /** @@ -445,6 +453,7 @@ public function getGroupGidNumber(string $dn) { public function getUserGidNumber(string $dn) { $gidNumber = false; if ($this->access->connection->hasGidNumber) { + // FIXME: when $dn does not exist on LDAP anymore, this will be set wrongly to false :/ $gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber); if ($gidNumber === false) { $this->access->connection->hasGidNumber = false; @@ -659,6 +668,25 @@ public function getUserPrimaryGroup(string $dn) { return false; } + private function isUserOnLDAP(string $uid): bool { + // forces a user exists check - but does not help if a positive result is cached, while group info is not + $ncUser = $this->ncUserManager->get($uid); + if ($ncUser === null) { + return false; + } + $backend = $ncUser->getBackend(); + if ($backend instanceof User_Proxy) { + // ignoring cache as safeguard (and we are behind the group cache check anyway) + return $backend->userExistsOnLDAP($uid, true); + } + return false; + } + + protected function getCachedGroupsForUserId(string $uid): array { + $groupStr = $this->config->getUserValue($uid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), '[]'); + return json_decode($groupStr) ?? []; + } + /** * This function fetches all groups a user belongs to. It does not check * if the user exists at all. @@ -686,8 +714,7 @@ public function getUserGroups($uid): array { if ($user instanceof OfflineUser) { // We load known group memberships from configuration for remnants, // because LDAP server does not contain them anymore - $groupStr = $this->config->getUserValue($uid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), '[]'); - return json_decode($groupStr) ?? []; + return $this->getCachedGroupsForUserId($uid); } $userDN = $this->access->username2dn($uid); @@ -801,8 +828,18 @@ public function getUserGroups($uid): array { $groups[] = $gidGroupName; } + if (empty($groups) && !$this->isUserOnLDAP($ncUid)) { + // Groups are enabled, but you user has none? Potentially suspicious: + // it could be that the user was deleted from LDAP, but we are not + // aware of it yet. + $groups = $this->getCachedGroupsForUserId($ncUid); + $this->access->connection->writeToCache($cacheKey, $groups); + return $groups; + } + $groups = array_unique($groups, SORT_LOCALE_STRING); $this->access->connection->writeToCache($cacheKey, $groups); + $groupStr = \json_encode($groups); $this->config->setUserValue($ncUid, 'user_ldap', 'cached-group-memberships-' . $this->access->connection->getConfigPrefix(), $groupStr); diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index 01a684662977b..ed4b47d85345e 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -36,6 +36,7 @@ use OCP\Group\Backend\INamedBackend; use OCP\GroupInterface; use OCP\IConfig; +use OCP\IUserManager; class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend, IBatchMethodsBackend { private $backends = []; @@ -44,6 +45,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet private GroupPluginManager $groupPluginManager; private bool $isSetUp = false; private IConfig $config; + private IUserManager $ncUserManager; public function __construct( Helper $helper, @@ -51,11 +53,13 @@ public function __construct( AccessFactory $accessFactory, GroupPluginManager $groupPluginManager, IConfig $config, + IUserManager $ncUserManager, ) { parent::__construct($ldap, $accessFactory); $this->helper = $helper; $this->groupPluginManager = $groupPluginManager; $this->config = $config; + $this->ncUserManager = $ncUserManager; } protected function setup(): void { @@ -66,7 +70,7 @@ protected function setup(): void { $serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true); foreach ($serverConfigPrefixes as $configPrefix) { $this->backends[$configPrefix] = - new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager, $this->config); + new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager, $this->config, $this->ncUserManager); if (is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index b4789b09c3d25..936d7db70f85d 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -38,8 +38,12 @@ use OCA\User_LDAP\Mapping\GroupMapping; use OCA\User_LDAP\User\Manager; use OCA\User_LDAP\User\OfflineUser; +use OCA\User_LDAP\User\User; +use OCA\User_LDAP\User_Proxy; use OCP\GroupInterface; use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -54,6 +58,7 @@ class Group_LDAPTest extends TestCase { private MockObject|Access $access; private MockObject|GroupPluginManager $pluginManager; private MockObject|IConfig $config; + private MockObject|IUserManager $ncUserManager; private GroupLDAP $groupBackend; public function setUp(): void { @@ -62,10 +67,11 @@ public function setUp(): void { $this->access = $this->getAccessMock(); $this->pluginManager = $this->createMock(GroupPluginManager::class); $this->config = $this->createMock(IConfig::class); + $this->ncUserManager = $this->createMock(IUserManager::class); } public function initBackend(): void { - $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager, $this->config); + $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager, $this->config, $this->ncUserManager); } public function testCountEmptySearchString() { @@ -786,12 +792,14 @@ public function testGetUserGroupsMemberOf() { $this->access->connection->hasPrimaryGroups = false; $this->access->connection->hasGidNumber = false; + $expectedGroups = ['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar']; + $this->access->expects($this->any()) ->method('username2dn') ->willReturn($dn); $this->access->expects($this->exactly(5)) ->method('readAttribute') - ->will($this->onConsecutiveCalls(['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar'], [], [], [], [])); + ->will($this->onConsecutiveCalls($expectedGroups, [], [], [], [])); $this->access->expects($this->any()) ->method('dn2groupname') ->willReturnArgument(0); @@ -802,6 +810,10 @@ public function testGetUserGroupsMemberOf() { ->method('isDNPartOfBase') ->willReturn(true); + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('userX', 'user_ldap', 'cached-group-memberships-', \json_encode($expectedGroups)); + $this->initBackend(); $groups = $this->groupBackend->getUserGroups('userX'); @@ -835,6 +847,34 @@ public function testGetUserGroupsMemberOfDisabled() { ->method('nextcloudGroupNames') ->willReturn([]); + // empty group result should not be oer + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('userX', 'user_ldap', 'cached-group-memberships-', '[]'); + + $ldapUser = $this->createMock(User::class); + + $this->access->userManager->expects($this->any()) + ->method('get') + ->with('userX') + ->willReturn($ldapUser); + + $userBackend = $this->createMock(User_Proxy::class); + $userBackend->expects($this->once()) + ->method('userExistsOnLDAP') + ->with('userX', true) + ->willReturn(true); + + $ncUser = $this->createMock(IUser::class); + $ncUser->expects($this->any()) + ->method('getBackend') + ->willReturn($userBackend); + + $this->ncUserManager->expects($this->once()) + ->method('get') + ->with('userX') + ->willReturn($ncUser); + $this->initBackend(); $this->groupBackend->getUserGroups('userX'); } @@ -861,6 +901,49 @@ public function testGetUserGroupsOfflineUser() { $this->assertTrue(in_array('groupF', $returnedGroups)); } + public function testGetUserGroupsUnrecognizedOfflineUser() { + $this->enableGroups(); + $dn = 'cn=userX,dc=foobar'; + + $ldapUser = $this->createMock(User::class); + + $userBackend = $this->createMock(User_Proxy::class); + $userBackend->expects($this->once()) + ->method('userExistsOnLDAP') + ->with('userX', true) + ->willReturn(false); + + $ncUser = $this->createMock(IUser::class); + $ncUser->expects($this->any()) + ->method('getBackend') + ->willReturn($userBackend); + + $this->config->expects($this->atLeastOnce()) + ->method('getUserValue') + ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything()) + ->willReturn(\json_encode(['groupB', 'groupF'])); + + $this->access->expects($this->any()) + ->method('username2dn') + ->willReturn($dn); + + $this->access->userManager->expects($this->any()) + ->method('get') + ->with('userX') + ->willReturn($ldapUser); + + $this->ncUserManager->expects($this->once()) + ->method('get') + ->with('userX') + ->willReturn($ncUser); + + $this->initBackend(); + $returnedGroups = $this->groupBackend->getUserGroups('userX'); + $this->assertCount(2, $returnedGroups); + $this->assertTrue(in_array('groupB', $returnedGroups)); + $this->assertTrue(in_array('groupF', $returnedGroups)); + } + public function nestedGroupsProvider(): array { return [ [true],