Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor _groupMembers to correctly use cache on intermediate results
Signed-off-by: Côme Chilliet <[email protected]>
  • Loading branch information
come-nc authored and CarlSchwan committed Oct 20, 2022
commit d07f43dc12addf21aaa76e6b330560b42d8f3ced
63 changes: 28 additions & 35 deletions apps/user_ldap/lib/Group_LDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,26 +240,15 @@ public function getDynamicGroupMembers(string $dnGroup): array {

/**
* Get group members from dn.
* @psalm-param array<string, int|array|string> $seen List of DN that have already been processed.
* @psalm-param array<string, bool> $seen List of DN that have already been processed.
* @throws ServerNotAvailableException
*/
private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
if ($seen === null) {
$seen = [];
// the root entry has to be marked as processed to avoid infinite loops,
// but not included in the results later on
$excludeFromResult = $dnGroup;
}
// cache only base groups, otherwise groups get additional unwarranted members
$shouldCacheResult = count($seen) === 0;

/** @psalm-var array<string, string[]|bool> $rawMemberReads */
static $rawMemberReads = []; // runtime cache for intermediate ldap read results
$allMembers = [];

if (array_key_exists($dnGroup, $seen)) {
private function _groupMembers(string $dnGroup, array &$seen = []): array {
if (isset($seen[$dnGroup])) {
return [];
}
$seen[$dnGroup] = true;

// used extensively in cron job, caching makes sense for nested groups
$cacheKey = '_groupMembers' . $dnGroup;
$groupMembers = $this->access->connection->getFromCache($cacheKey);
Expand Down Expand Up @@ -288,47 +277,51 @@ private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
return $carry;
}, []);
if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) {
$this->access->connection->writeToCache($cacheKey, $result);
return $result;
} elseif (!empty($memberRecords)) {
$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE;
$this->access->connection->saveConfiguration();
$this->access->connection->writeToCache($cacheKey, $result);
return $result;
}
// when feature availability is unknown, and the result is empty, continue and test with original approach
}

$seen[$dnGroup] = 1;
$members = $rawMemberReads[$dnGroup] ?? null;
if ($members === null) {
$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
$rawMemberReads[$dnGroup] = $members;
}
$allMembers = [];
$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
if (is_array($members)) {
$fetcher = function (string $memberDN) use (&$seen) {
return $this->_groupMembers($memberDN, $seen);
};
$allMembers = $this->walkNestedGroupsReturnDNs($dnGroup, $fetcher, $members, $seen);
if ((int)$this->access->connection->ldapNestedGroups === 1) {
while ($recordDn = array_shift($members)) {
$nestedMembers = $this->_groupMembers($recordDn, $seen);
$members = array_merge($members, $nestedMembers);
$allMembers[] = $recordDn;
}
} else {
$allMembers = $members;
}
}

$allMembers += $this->getDynamicGroupMembers($dnGroup);
if (isset($excludeFromResult)) {
$index = array_search($excludeFromResult, $allMembers, true);
if ($index !== false) {
unset($allMembers[$index]);
}
}

if ($shouldCacheResult) {
$this->access->connection->writeToCache($cacheKey, $allMembers);
unset($rawMemberReads[$dnGroup]);
$allMembers = array_unique($allMembers);

// A group cannot be a member of itself
$index = array_search($dnGroup, $allMembers, true);
if ($index !== false) {
unset($allMembers[$index]);
}

$this->access->connection->writeToCache($cacheKey, $allMembers);

if (isset($attemptedLdapMatchingRuleInChain)
&& $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN
&& !empty($allMembers)
) {
$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE;
$this->access->connection->saveConfiguration();
}

return $allMembers;
}

Expand Down