diff --git a/apps/user_ldap/lib/IUserLDAP.php b/apps/user_ldap/lib/IUserLDAP.php index 6e730fbe94158..7d8e0fd5f53a4 100644 --- a/apps/user_ldap/lib/IUserLDAP.php +++ b/apps/user_ldap/lib/IUserLDAP.php @@ -8,6 +8,8 @@ */ namespace OCA\User_LDAP; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; + interface IUserLDAP { //Functions used by LDAPProvider @@ -32,4 +34,14 @@ public function getNewLDAPConnection($uid); * @return string|false with the username */ public function dn2UserName($dn); + + /** + * Fetches one user from LDAP based on a filter or a custom attribute and search term. + * + * @param string $attribute The LDAP attribute name to search against (e.g., 'mail', 'cn', 'uid'). + * @param string $searchTerm The search term to match against the attribute. Will be escaped for LDAP filter safety. + * @return string|null Returns the username if found in LDAP using the configured LDAP filter, or null if no user is found. + * @throws MultipleUsersReturnedException if multiple users have been found (search query should not allow this) + */ + public function getUserFromCustomAttribute(string $attribute, string $searchTerm): ?string; } diff --git a/apps/user_ldap/lib/LDAPProvider.php b/apps/user_ldap/lib/LDAPProvider.php index 31a40fa89e2d1..2acc9ebe49a42 100644 --- a/apps/user_ldap/lib/LDAPProvider.php +++ b/apps/user_ldap/lib/LDAPProvider.php @@ -12,6 +12,7 @@ use OCA\User_LDAP\User\DeletedUsersIndex; use OCP\GroupInterface; use OCP\IGroupManager; +use OCP\IUser; use OCP\IUserManager; use OCP\LDAP\IDeletionFlagSupport; use OCP\LDAP\ILDAPProvider; @@ -29,7 +30,7 @@ class LDAPProvider implements ILDAPProvider, IDeletionFlagSupport { * @throws \Exception if user_ldap app was not enabled */ public function __construct( - IUserManager $userManager, + private IUserManager $userManager, IGroupManager $groupManager, private Helper $helper, private DeletedUsersIndex $deletedUsersIndex, @@ -37,7 +38,7 @@ public function __construct( ) { $userBackendFound = false; $groupBackendFound = false; - foreach ($userManager->getBackends() as $backend) { + foreach ($this->userManager->getBackends() as $backend) { $this->logger->debug('instance ' . get_class($backend) . ' user backend.', ['app' => 'user_ldap']); if ($backend instanceof IUserLDAP) { $this->userBackend = $backend; @@ -301,4 +302,12 @@ public function getMultiValueUserAttribute(string $uid, string $attribute): arra $connection->writeToCache($key, $values); return $values; } + + public function findOneUserByAttributeValue(string $attribute, string $searchTerm): ?IUser { + $userId = $this->userBackend->getUserFromCustomAttribute($attribute, $searchTerm); + if (!$userId) { + return null; + } + return $this->userManager->get($userId); + } } diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php index 22475ceccab6e..f78b0e2f53e23 100644 --- a/apps/user_ldap/lib/User_LDAP.php +++ b/apps/user_ldap/lib/User_LDAP.php @@ -16,21 +16,23 @@ use OCA\User_LDAP\User\User; use OCP\Accounts\IAccountManager; use OCP\IUserBackend; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; use OCP\Notification\IManager as INotificationManager; use OCP\User\Backend\ICountMappedUsersBackend; use OCP\User\Backend\ILimitAwareCountUsersBackend; use OCP\User\Backend\IPropertyPermissionBackend; use OCP\User\Backend\IProvideEnabledStateBackend; use OCP\UserInterface; +use Override; use Psr\Log\LoggerInterface; class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, IUserLDAP, ILimitAwareCountUsersBackend, ICountMappedUsersBackend, IProvideEnabledStateBackend, IPropertyPermissionBackend { public function __construct( Access $access, - protected INotificationManager $notificationManager, - protected UserPluginManager $userPluginManager, - protected LoggerInterface $logger, - protected DeletedUsersIndex $deletedUsersIndex, + protected readonly INotificationManager $notificationManager, + protected readonly UserPluginManager $userPluginManager, + protected readonly LoggerInterface $logger, + protected readonly DeletedUsersIndex $deletedUsersIndex, ) { parent::__construct($access); } @@ -684,4 +686,25 @@ public function canEditProperty(string $uid, string $property): bool { default => true, }; } + + #[Override] + public function getUserFromCustomAttribute(string $attribute, string $searchTerm): ?string { + $searchTerm = $this->access->escapeFilterPart($searchTerm); + $attribute = $this->access->escapeFilterPart($attribute); + + $filter = "($attribute=$searchTerm)"; + + $records = $this->access->searchUsers($filter, ['dn']); + if (count($records) === 1) { + $ldapUser = $this->access->userManager->get($records[0]['dn'][0]); + return $ldapUser->getUsername(); + } elseif (count($records) > 1) { + $this->logger->error( + 'Multiple users found for filter: ' . $filter, + ['app' => 'user_ldap'] + ); + throw new MultipleUsersReturnedException(); + } + return null; + } } diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php index 6a01ab3483019..9a1d2ed0512d0 100644 --- a/apps/user_ldap/lib/User_Proxy.php +++ b/apps/user_ldap/lib/User_Proxy.php @@ -11,6 +11,7 @@ use OCA\User_LDAP\User\OfflineUser; use OCA\User_LDAP\User\User; use OCP\IUserBackend; +use OCP\LDAP\Exceptions\MultipleUsersReturnedException; use OCP\Notification\IManager as INotificationManager; use OCP\User\Backend\ICountMappedUsersBackend; use OCP\User\Backend\IGetDisplayNameBackend; @@ -18,6 +19,7 @@ use OCP\User\Backend\IPropertyPermissionBackend; use OCP\User\Backend\IProvideEnabledStateBackend; use OCP\UserInterface; +use Override; use Psr\Log\LoggerInterface; /** @@ -25,13 +27,13 @@ */ class User_Proxy extends Proxy implements IUserBackend, UserInterface, IUserLDAP, ILimitAwareCountUsersBackend, ICountMappedUsersBackend, IProvideEnabledStateBackend, IGetDisplayNameBackend, IPropertyPermissionBackend { public function __construct( - private Helper $helper, + Helper $helper, ILDAPWrapper $ldap, AccessFactory $accessFactory, - private INotificationManager $notificationManager, - private UserPluginManager $userPluginManager, - private LoggerInterface $logger, - private DeletedUsersIndex $deletedUsersIndex, + private readonly INotificationManager $notificationManager, + private readonly UserPluginManager $userPluginManager, + private readonly LoggerInterface $logger, + private readonly DeletedUsersIndex $deletedUsersIndex, ) { parent::__construct($helper, $ldap, $accessFactory); } @@ -437,4 +439,19 @@ public function getDisabledUserList(?int $limit = null, int $offset = 0, string public function canEditProperty(string $uid, string $property): bool { return $this->handleRequest($uid, 'canEditProperty', [$uid, $property]); } + + #[Override] + public function getUserFromCustomAttribute(string $attribute, string $searchTerm): ?string { + $this->setup(); + $user = null; + foreach ($this->backends as $backend) { + $fetchUser = $backend->getUserFromCustomAttribute($attribute, $searchTerm); + // if we found a different user, no need to continue + if ($user !== null && $fetchUser !== null && $fetchUser !== $user) { + throw new MultipleUsersReturnedException('Multiple users found for custom attribute search'); + } + $user = $fetchUser; // may be null + } + return $user; + } } diff --git a/lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php b/lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php new file mode 100644 index 0000000000000..35fca39f5eb3c --- /dev/null +++ b/lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php @@ -0,0 +1,20 @@ +