diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index 04778e4f..071f50db 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -56,6 +56,7 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; use OCP\Session\Exceptions\SessionNotAvailableException; @@ -634,14 +635,28 @@ public function code(string $state = '', string $code = '', string $scope = '', $softAutoProvisionAllowed = (!isset($oidcSystemConfig['soft_auto_provision']) || $oidcSystemConfig['soft_auto_provision']); $shouldDoUserLookup = !$autoProvisionAllowed || ($softAutoProvisionAllowed && !$this->provisioningService->hasOidcUserProvisitioned($userId)); + $existingUser = null; if ($shouldDoUserLookup && $this->ldapService->isLDAPEnabled()) { - // in case user is provisioned by user_ldap, userManager->search() triggers an ldap search which syncs the results - // so new users will be directly available even if they were not synced before this login attempt - $this->userManager->search($userId, 1, 0); - $this->ldapService->syncUser($userId); + $ldapMappingAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_LDAP_MAPPING_ATTRIBUTE); + if ($ldapMappingAttribute !== '') { + // Find a user in LDAP with a custom attribute equal to the user id given in OIDC + try { + $existingUser = $this->ldapService->findUserByAttribute($ldapMappingAttribute, $userId); + } catch (MultipleUsersReturnedException $e) { + $message = $this->l10n->t('The configured LDAP mapping attribute returned more than one users. Please contact an administrator.'); + return $this->build403TemplateResponse($message, Http::STATUS_FORBIDDEN); + } + } else { + // in case user is provisioned by user_ldap, userManager->search() triggers an ldap search which syncs the results + // so new users will be directly available even if they were not synced before this login attempt + $this->userManager->search($userId, 1, 0); + $this->ldapService->syncUser($userId); + } } - $existingUser = $this->userManager->get($userId); + if ($existingUser === null) { + $existingUser = $this->userManager->get($userId); + } if ($existingUser !== null && $this->ldapService->isLdapDeletedUser($existingUser)) { $existingUser = null; } diff --git a/lib/Service/LdapService.php b/lib/Service/LdapService.php index a616bce6..c5ec419a 100644 --- a/lib/Service/LdapService.php +++ b/lib/Service/LdapService.php @@ -10,7 +10,9 @@ namespace OCA\UserOIDC\Service; use OCP\App\IAppManager; +use OCP\IConfig; use OCP\IUser; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; use OCP\Server; use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; @@ -79,4 +81,15 @@ public function syncUser(string $userId): void { $this->logger->debug('\OCA\User_LDAP\User_Proxy class not found'); } } + + /** + * @throws MultipleUsersReturnedException + */ + public function findUserByAttribute(string $attribute, string $searchTerm): ?IUser { + if (version_compare(Server::get(IConfig::class)->getSystemValueString('version', '0.0.0'), '34.0.0', '>=')) { + $ldapUserProxy = Server::get(\OCA\User_LDAP\User_Proxy::class); + return $ldapUserProxy->getUserFromCustomAttribute($attribute, $searchTerm); + } + return null; + } } diff --git a/lib/Service/ProviderService.php b/lib/Service/ProviderService.php index f5a82a51..27fce4fd 100644 --- a/lib/Service/ProviderService.php +++ b/lib/Service/ProviderService.php @@ -61,6 +61,7 @@ class ProviderService { public const SETTING_RESTRICT_LOGIN_TO_GROUPS = 'restrictLoginToGroups'; public const SETTING_AZURE_GROUP_NAMES = 'azureGroupNames'; public const SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING = 'nestedAndFallbackClaims'; + public const SETTING_LDAP_MAPPING_ATTRIBUTE = 'mappingAttribute'; public const BOOLEAN_SETTINGS_DEFAULT_VALUES = [ self::SETTING_GROUP_PROVISIONING => false, diff --git a/lib/User/Backend.php b/lib/User/Backend.php index 9e120c16..1e9610fd 100644 --- a/lib/User/Backend.php +++ b/lib/User/Backend.php @@ -22,6 +22,7 @@ use OCA\UserOIDC\User\Validator\SelfEncodedValidator; use OCA\UserOIDC\User\Validator\UserInfoValidator; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Authentication\IApacheBackend; use OCP\DB\Exception; @@ -38,6 +39,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; use OCP\Server; use OCP\User\Backend\ABackend; use OCP\User\Backend\ICountUsersBackend; @@ -334,13 +336,11 @@ public function getCurrentUserId(): string { if ($autoProvisionAllowed) { // look for user in other backends - if (!$this->userManager->userExists($tokenUserId)) { - $this->userManager->search($tokenUserId); - $this->ldapService->syncUser($tokenUserId); - } - $existingUser = $this->userManager->get($tokenUserId); - if ($existingUser !== null && $this->ldapService->isLdapDeletedUser($existingUser)) { - $existingUser = null; + try { + $existingUser = $this->lookupUserInLDAP($provider, $tokenUserId); + } catch (MultipleUsersReturnedException $e) { + $this->logger->debug('The configured LDAP mapping attribute returned more than one users'); + return ''; } $softAutoProvisionAllowed = (!isset($oidcSystemConfig['soft_auto_provision']) || $oidcSystemConfig['soft_auto_provision']); @@ -378,18 +378,13 @@ public function getCurrentUserId(): string { // check if the user exists locally // if not, this potentially triggers a user_ldap search // to get the user if it has not been synced yet - if (!$this->userManager->userExists($tokenUserId)) { - $this->userManager->search($tokenUserId); - $this->ldapService->syncUser($tokenUserId); - - // return nothing, if the user was not found after the user_ldap search - if (!$this->userManager->userExists($tokenUserId)) { - return ''; - } + try { + $existingUser = $this->lookupUserInLDAP($provider, $tokenUserId); + } catch (MultipleUsersReturnedException $e) { + $this->logger->debug('The configured LDAP mapping attribute returned more than one users'); + return ''; } - - $user = $this->userManager->get($tokenUserId); - if ($user === null || $this->ldapService->isLdapDeletedUser($user)) { + if ($existingUser === null) { return ''; } $this->checkFirstLogin($tokenUserId); @@ -406,6 +401,31 @@ public function getCurrentUserId(): string { return ''; } + /** + * @throws MultipleUsersReturnedException + */ + private function lookupUserInLDAP(Provider $provider, string $tokenUserId): ?IUser { + $existingUser = null; + if ($this->ldapService->isLDAPEnabled()) { + $ldapMappingAttribute = $this->providerService->getSetting($provider->getId(), ProviderService::SETTING_LDAP_MAPPING_ATTRIBUTE); + if ($ldapMappingAttribute !== '') { + // Find a user in LDAP with a custom attribute equal to the user id given in OIDC + $existingUser = $this->ldapService->findUserByAttribute($ldapMappingAttribute, $tokenUserId); + } else if (!$this->userManager->userExists($tokenUserId)) { + $this->userManager->search($tokenUserId); + $this->ldapService->syncUser($tokenUserId); + } + + if ($existingUser === null) { + $existingUser = $this->userManager->get($tokenUserId); + } + if ($existingUser !== null && $this->ldapService->isLdapDeletedUser($existingUser)) { + $existingUser = null; + } + } + return $existingUser; + } + /** * Returns true only if $userId is a non-empty, non-whitespace-only string. * Used as a lightweight sanity check on user IDs returned by token validators