Skip to content

Commit 6d2471d

Browse files
authored
Merge pull request #23566 from nextcloud/fix/noid/groupsbymember-apply-group-filter-early-if-possible
LDAP: when nesting is not enabled, the group filter can be applied right away
2 parents 8355f95 + 2ee26b6 commit 6d2471d

File tree

3 files changed

+43
-12
lines changed

3 files changed

+43
-12
lines changed

apps/user_ldap/lib/Connection.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,7 @@ public function __clone() {
145145
$this->dontDestruct = true;
146146
}
147147

148-
/**
149-
* @param string $name
150-
* @return bool|mixed
151-
*/
152-
public function __get($name) {
148+
public function __get(string $name) {
153149
if (!$this->configured) {
154150
$this->readConfiguration();
155151
}

apps/user_ldap/lib/Group_LDAP.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ private function walkNestedGroups(string $dn, Closure $fetcher, array $list): ar
344344
}
345345

346346
$seen = [];
347-
while ($record = array_pop($list)) {
347+
while ($record = array_shift($list)) {
348348
$recordDN = $recordMode ? $record['dn'][0] : $record;
349349
if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
350350
// Prevent loops
@@ -822,6 +822,11 @@ private function getGroupsByMember(string $dn, array &$seen = null): array {
822822
$filter .= '@*';
823823
}
824824

825+
$nesting = (int)$this->access->connection->ldapNestedGroups;
826+
if ($nesting === 0) {
827+
$filter = $this->access->combineFilterWithAnd([$filter, $this->access->connection->ldapGroupFilter]);
828+
}
829+
825830
$groups = $this->access->fetchListOfGroups($filter,
826831
[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
827832
if (is_array($groups)) {

apps/user_ldap/tests/Group_LDAPTest.php

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -719,23 +719,36 @@ public function testGetUserGroupsMemberOfDisabled() {
719719
$groupBackend->getUserGroups('userX');
720720
}
721721

722-
public function testGetGroupsByMember() {
722+
public function nestedGroupsProvider(): array {
723+
return [
724+
[ true ],
725+
[ false ],
726+
];
727+
}
728+
729+
/**
730+
* @dataProvider nestedGroupsProvider
731+
*/
732+
public function testGetGroupsByMember(bool $nestedGroups) {
723733
$access = $this->getAccessMock();
724734
$pluginManager = $this->getPluginManagerMock();
725735

736+
$groupFilter = '(&(objectclass=nextcloudGroup)(nextcloudEnabled=TRUE))';
726737
$access->connection = $this->createMock(Connection::class);
727738
$access->connection->expects($this->any())
728739
->method('__get')
729-
->willReturnCallback(function ($name) {
740+
->willReturnCallback(function ($name) use ($nestedGroups, $groupFilter) {
730741
switch ($name) {
731742
case 'useMemberOfToDetectMembership':
732743
return 0;
733744
case 'ldapDynamicGroupMemberURL':
734745
return '';
735746
case 'ldapNestedGroups':
736-
return false;
747+
return $nestedGroups;
737748
case 'ldapGroupMemberAssocAttr':
738749
return 'member';
750+
case 'ldapGroupFilter':
751+
return $groupFilter;
739752
}
740753
return 1;
741754
});
@@ -748,10 +761,15 @@ public function testGetGroupsByMember() {
748761
$access->expects($this->exactly(2))
749762
->method('username2dn')
750763
->willReturn($dn);
751-
752764
$access->expects($this->any())
753765
->method('readAttribute')
754766
->willReturn([]);
767+
$access->expects($this->any())
768+
->method('combineFilterWithAnd')
769+
->willReturnCallback(function (array $filterParts) {
770+
// ⚠ returns a pseudo-filter only, not real LDAP Filter syntax
771+
return implode('&', $filterParts);
772+
});
755773

756774
$group1 = [
757775
'cn' => 'group1',
@@ -766,9 +784,21 @@ public function testGetGroupsByMember() {
766784
->method('nextcloudGroupNames')
767785
->with([$group1, $group2])
768786
->willReturn(['group1', 'group2']);
769-
$access->expects($this->once())
787+
$access->expects($nestedGroups ? $this->atLeastOnce() : $this->once())
770788
->method('fetchListOfGroups')
771-
->willReturn([$group1, $group2]);
789+
->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2) {
790+
static $firstRun = true;
791+
if (!$nestedGroups) {
792+
// When nested groups are enabled, groups cannot be filtered early as it would
793+
// exclude intermediate groups. But we can, and should, when working with flat groups.
794+
$this->assertTrue(strpos($filter, $groupFilter) !== false);
795+
}
796+
if ($firstRun) {
797+
$firstRun = false;
798+
return [$group1, $group2];
799+
}
800+
return [];
801+
});
772802
$access->expects($this->any())
773803
->method('dn2groupname')
774804
->willReturnCallback(function (string $dn) {

0 commit comments

Comments
 (0)