aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_ldap
diff options
context:
space:
mode:
authorCôme Chilliet <come.chilliet@nextcloud.com>2024-06-25 16:57:02 +0200
committerCôme Chilliet <come.chilliet@nextcloud.com>2024-08-27 10:05:12 +0200
commit4fb1d2f3e5efd1272fa41700a489c80b1a367997 (patch)
tree6ff60b0ac54bff7fb6a638a81265e55f595ef5eb /apps/user_ldap
parentb8ab7b7e556d7bc71597a2ca716652b80174719d (diff)
downloadnextcloud-server-4fb1d2f3e5efd1272fa41700a489c80b1a367997.tar.gz
nextcloud-server-4fb1d2f3e5efd1272fa41700a489c80b1a367997.zip
fix(user_ldap): Avoid extra LDAP request when mapping a user for the first time
Avoids using several LDAP searches to get UUID, display name and internal name, now gets all attributes at the same time. Also avoids extra request to build an unused user object in userExists. Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
Diffstat (limited to 'apps/user_ldap')
-rw-r--r--apps/user_ldap/lib/Access.php116
-rw-r--r--apps/user_ldap/lib/User/Manager.php36
-rw-r--r--apps/user_ldap/lib/User_LDAP.php5
3 files changed, 133 insertions, 24 deletions
diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php
index b97079610b4..da09fa0ff07 100644
--- a/apps/user_ldap/lib/Access.php
+++ b/apps/user_ldap/lib/Access.php
@@ -117,6 +117,56 @@ class Access extends LDAPUtility {
}
/**
+ * Reads several attributes for an LDAP record identified by a DN and a filter
+ * No support for ranged attributes.
+ *
+ * @param string $dn the record in question
+ * @param array $attrs the attributes that shall be retrieved
+ * if empty, just check the record's existence
+ * @param string $filter
+ * @return array|false an array of values on success or an empty
+ * array if $attr is empty, false otherwise
+ * @throws ServerNotAvailableException
+ */
+ public function readAttributes(string $dn, array $attrs, string $filter = 'objectClass=*'): array|false {
+ if (!$this->checkConnection()) {
+ $this->logger->warning(
+ 'No LDAP Connector assigned, access impossible for readAttribute.',
+ ['app' => 'user_ldap']
+ );
+ return false;
+ }
+ $cr = $this->connection->getConnectionResource();
+ $attrs = array_map(
+ fn (string $attr): string => mb_strtolower($attr, 'UTF-8'),
+ $attrs,
+ );
+
+ $values = [];
+ $record = $this->executeRead($dn, $attrs, $filter);
+ if (is_bool($record)) {
+ // when an exists request was run and it was successful, an empty
+ // array must be returned
+ return $record ? [] : false;
+ }
+
+ $result = [];
+ foreach ($attrs as $attr) {
+ $values = $this->extractAttributeValuesFromResult($record, $attr);
+ if (!empty($values)) {
+ $result[$attr] = $values;
+ }
+ }
+
+ if (!empty($result)) {
+ return $result;
+ }
+
+ $this->logger->debug('Requested attributes {attrs} not found for ' . $dn, ['app' => 'user_ldap', 'attrs' => $attrs]);
+ return false;
+ }
+
+ /**
* reads a given attribute for an LDAP record identified by a DN
*
* @param string $dn the record in question
@@ -191,9 +241,9 @@ class Access extends LDAPUtility {
* returned data on a successful usual operation
* @throws ServerNotAvailableException
*/
- public function executeRead(string $dn, string $attribute, string $filter) {
+ public function executeRead(string $dn, string|array $attribute, string $filter) {
$dn = $this->helper->DNasBaseParameter($dn);
- $rr = @$this->invokeLDAPMethod('read', $dn, $filter, [$attribute]);
+ $rr = @$this->invokeLDAPMethod('read', $dn, $filter, (is_string($attribute) ? [$attribute] : $attribute));
if (!$this->ldap->isResource($rr)) {
if ($attribute !== '') {
//do not throw this message on userExists check, irritates
@@ -410,7 +460,7 @@ class Access extends LDAPUtility {
* @return string|false with with the name to use in Nextcloud
* @throws \Exception
*/
- public function dn2username($fdn, $ldapName = null) {
+ public function dn2username($fdn) {
//To avoid bypassing the base DN settings under certain circumstances
//with the group support, check whether the provided DN matches one of
//the given Bases
@@ -418,7 +468,7 @@ class Access extends LDAPUtility {
return false;
}
- return $this->dn2ocname($fdn, $ldapName, true);
+ return $this->dn2ocname($fdn, null, true);
}
/**
@@ -441,12 +491,8 @@ class Access extends LDAPUtility {
$newlyMapped = false;
if ($isUser) {
$mapper = $this->getUserMapper();
- $nameAttribute = $this->connection->ldapUserDisplayName;
- $filter = $this->connection->ldapUserFilter;
} else {
$mapper = $this->getGroupMapper();
- $nameAttribute = $this->connection->ldapGroupDisplayName;
- $filter = $this->connection->ldapGroupFilter;
}
//let's try to retrieve the Nextcloud name from the mappings table
@@ -455,6 +501,36 @@ class Access extends LDAPUtility {
return $ncName;
}
+ if ($isUser) {
+ $nameAttribute = $this->connection->ldapUserDisplayName;
+ $filter = $this->connection->ldapUserFilter;
+ $uuidAttr = 'ldapUuidUserAttribute';
+ $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
+ $usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
+ $attributesToRead = [$nameAttribute,$usernameAttribute];
+ // TODO fetch also display name attributes and cache them if the user is mapped
+ } else {
+ $nameAttribute = $this->connection->ldapGroupDisplayName;
+ $filter = $this->connection->ldapGroupFilter;
+ $uuidAttr = 'ldapUuidGroupAttribute';
+ $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
+ $attributesToRead = [$nameAttribute];
+ }
+
+ if ($this->detectUuidAttribute($fdn, $isUser, false, $record)) {
+ $attributesToRead[] = $this->connection->$uuidAttr;
+ }
+
+ if ($record === null) {
+ /* No record was passed, fetch it */
+ $record = $this->readAttributes($fdn, $attributesToRead, $filter);
+ if ($record === false) {
+ $this->logger->debug('Cannot read attributes for ' . $fdn . '. Skipping.', ['filter' => $filter]);
+ $intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
+ return false;
+ }
+ }
+
//second try: get the UUID and check if it is known. Then, update the DN and return the name.
$uuid = $this->getUUID($fdn, $isUser, $record);
if (is_string($uuid)) {
@@ -469,20 +545,9 @@ class Access extends LDAPUtility {
return false;
}
- if (is_null($ldapName)) {
- $ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
- if (!isset($ldapName[0]) || empty($ldapName[0])) {
- $this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']);
- $intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
- return false;
- }
- $ldapName = $ldapName[0];
- }
-
if ($isUser) {
- $usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
if ($usernameAttribute !== '') {
- $username = $this->readAttribute($fdn, $usernameAttribute);
+ $username = $record[$usernameAttribute];
if (!isset($username[0]) || empty($username[0])) {
$this->logger->debug('No or empty username (' . $usernameAttribute . ') for ' . $fdn . '.', ['app' => 'user_ldap']);
return false;
@@ -504,6 +569,15 @@ class Access extends LDAPUtility {
return false;
}
} else {
+ if (is_null($ldapName)) {
+ $ldapName = $record[$nameAttribute];
+ if (!isset($ldapName[0]) || empty($ldapName[0])) {
+ $this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']);
+ $intermediates['group-' . $fdn] = true;
+ return false;
+ }
+ $ldapName = $ldapName[0];
+ }
$intName = $this->sanitizeGroupIDCandidate($ldapName);
}
@@ -521,6 +595,7 @@ class Access extends LDAPUtility {
$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
$newlyMapped = $this->mapAndAnnounceIfApplicable($mapper, $fdn, $intName, $uuid, $isUser);
if ($newlyMapped) {
+ $this->logger->debug('Mapped {fdn} as {name}', ['fdn' => $fdn,'name' => $intName]);
return $intName;
}
}
@@ -535,7 +610,6 @@ class Access extends LDAPUtility {
'fdn' => $fdn,
'altName' => $altName,
'intName' => $intName,
- 'app' => 'user_ldap',
]
);
$newlyMapped = true;
diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php
index c2365f55432..a671570be04 100644
--- a/apps/user_ldap/lib/User/Manager.php
+++ b/apps/user_ldap/lib/User/Manager.php
@@ -106,6 +106,7 @@ class Manager {
/**
* @brief checks whether the Access instance has been set
* @throws \Exception if Access has not been set
+ * @psalm-assert !null $this->access
* @return null
*/
private function checkAccess() {
@@ -237,4 +238,39 @@ class Manager {
return $this->createInstancyByUserName($id);
}
+
+ /**
+ * @brief Checks whether a User object by its DN or Nextcloud username exists
+ * @param string $id the DN or username of the user
+ * @throws \Exception when connection could not be established
+ */
+ public function exists($id): bool {
+ $this->checkAccess();
+ $this->logger->debug('Checking if {id} exists', ['id' => $id]);
+ if (isset($this->usersByDN[$id])) {
+ return true;
+ } elseif (isset($this->usersByUid[$id])) {
+ return true;
+ }
+
+ if ($this->access->stringResemblesDN($id)) {
+ $this->logger->debug('{id} looks like a dn', ['id' => $id]);
+ $uid = $this->access->dn2username($id);
+ if ($uid !== false) {
+ return true;
+ }
+ }
+
+ // Most likely a uid. Check whether it is a deleted user
+ if ($this->isDeletedUser($id)) {
+ return true;
+ }
+ $this->logger->debug('username2dn({id})', ['id' => $id]);
+ $dn = $this->access->username2dn($id);
+ $this->logger->debug('end username2dn({id})', ['id' => $id]);
+ if ($dn !== false) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php
index 8d29d7c7588..1f001a33db1 100644
--- a/apps/user_ldap/lib/User_LDAP.php
+++ b/apps/user_ldap/lib/User_LDAP.php
@@ -322,10 +322,9 @@ class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, I
if (!is_null($userExists)) {
return (bool)$userExists;
}
- //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
- $user = $this->access->userManager->get($uid);
+ $userExists = $this->access->userManager->exists($uid);
- if (is_null($user)) {
+ if (!$userExists) {
$this->logger->debug(
'No DN found for '.$uid.' on '.$this->access->connection->ldapHost,
['app' => 'user_ldap']