mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #59928 from nextcloud/carl/ldap-search-one-by-attribute
feat(ldap): Allow to search one user by one of its LDAP attribute
This commit is contained in:
commit
4eb8bc8d50
10 changed files with 112 additions and 13 deletions
|
|
@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
|||
class ComposerStaticInitDAV
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\DAV\\' => 8,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\DAV\\' =>
|
||||
'OCA\\DAV\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OCA\User_LDAP;
|
||||
|
||||
use OCP\LDAP\Exceptions\MultipleUsersReturnedException;
|
||||
|
||||
interface IUserLDAP {
|
||||
|
||||
//Functions used by LDAPProvider
|
||||
|
|
@ -32,4 +34,14 @@ interface IUserLDAP {
|
|||
* @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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use LDAP\Connection;
|
|||
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 @@ class LDAPProvider implements ILDAPProvider, IDeletionFlagSupport {
|
|||
) {
|
||||
$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;
|
||||
|
|
@ -320,4 +321,13 @@ class LDAPProvider implements ILDAPProvider, IDeletionFlagSupport {
|
|||
$connection->writeToCache($key, $values);
|
||||
return $values;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findOneUserByAttributeValue(string $attribute, string $searchTerm): ?IUser {
|
||||
$userId = $this->userBackend->getUserFromCustomAttribute($attribute, $searchTerm);
|
||||
if (!$userId) {
|
||||
return null;
|
||||
}
|
||||
return $this->userManager->get($userId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,21 +16,23 @@ use OCA\User_LDAP\User\OfflineUser;
|
|||
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);
|
||||
}
|
||||
|
|
@ -701,4 +703,25 @@ class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, I
|
|||
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']);
|
||||
$this->logger->error($filter);
|
||||
if (count($records) === 1) {
|
||||
return $this->access->dn2username($records[0]['dn'][0]) ?: null;
|
||||
} elseif (count($records) > 1) {
|
||||
$this->logger->error(
|
||||
'Multiple users found for filter: ' . $filter,
|
||||
['app' => 'user_ldap']
|
||||
);
|
||||
throw new MultipleUsersReturnedException();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use OCA\User_LDAP\User\DeletedUsersIndex;
|
|||
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\ILimitAwareCountUsersBackend;
|
|||
use OCP\User\Backend\IPropertyPermissionBackend;
|
||||
use OCP\User\Backend\IProvideEnabledStateBackend;
|
||||
use OCP\UserInterface;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -25,13 +27,13 @@ use Psr\Log\LoggerInterface;
|
|||
*/
|
||||
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);
|
||||
}
|
||||
|
|
@ -458,4 +460,19 @@ class User_Proxy extends Proxy implements IUserBackend, UserInterface, IUserLDAP
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
|
@ -17,3 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
|
|
|||
|
|
@ -659,6 +659,7 @@ return array(
|
|||
'OCP\\Install\\Events\\InstallationCompletedEvent' => $baseDir . '/lib/public/Install/Events/InstallationCompletedEvent.php',
|
||||
'OCP\\L10N\\IFactory' => $baseDir . '/lib/public/L10N/IFactory.php',
|
||||
'OCP\\L10N\\ILanguageIterator' => $baseDir . '/lib/public/L10N/ILanguageIterator.php',
|
||||
'OCP\\LDAP\\Exceptions\\MultipleUsersReturnedException' => $baseDir . '/lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php',
|
||||
'OCP\\LDAP\\IDeletionFlagSupport' => $baseDir . '/lib/public/LDAP/IDeletionFlagSupport.php',
|
||||
'OCP\\LDAP\\ILDAPProvider' => $baseDir . '/lib/public/LDAP/ILDAPProvider.php',
|
||||
'OCP\\LDAP\\ILDAPProviderFactory' => $baseDir . '/lib/public/LDAP/ILDAPProviderFactory.php',
|
||||
|
|
|
|||
|
|
@ -700,6 +700,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Install\\Events\\InstallationCompletedEvent' => __DIR__ . '/../../..' . '/lib/public/Install/Events/InstallationCompletedEvent.php',
|
||||
'OCP\\L10N\\IFactory' => __DIR__ . '/../../..' . '/lib/public/L10N/IFactory.php',
|
||||
'OCP\\L10N\\ILanguageIterator' => __DIR__ . '/../../..' . '/lib/public/L10N/ILanguageIterator.php',
|
||||
'OCP\\LDAP\\Exceptions\\MultipleUsersReturnedException' => __DIR__ . '/../../..' . '/lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php',
|
||||
'OCP\\LDAP\\IDeletionFlagSupport' => __DIR__ . '/../../..' . '/lib/public/LDAP/IDeletionFlagSupport.php',
|
||||
'OCP\\LDAP\\ILDAPProvider' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProvider.php',
|
||||
'OCP\\LDAP\\ILDAPProviderFactory' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProviderFactory.php',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\LDAP\Exceptions;
|
||||
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
|
||||
/**
|
||||
* Exception for a ldap search that unexpectedly returns multiple users.
|
||||
*
|
||||
* @since 34.0.0
|
||||
*/
|
||||
#[Consumable(since: '34.0.0')]
|
||||
class MultipleUsersReturnedException extends \Exception {
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ namespace OCP\LDAP;
|
|||
|
||||
use LDAP\Connection;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\IUser;
|
||||
use OCP\LDAP\Exceptions\MultipleUsersReturnedException;
|
||||
|
||||
/**
|
||||
* Interface ILDAPProvider
|
||||
|
|
@ -154,4 +156,15 @@ interface ILDAPProvider {
|
|||
* @since 22.0.0
|
||||
*/
|
||||
public function getMultiValueUserAttribute(string $uid, string $attribute): array;
|
||||
|
||||
/**
|
||||
* Search for a single user in LDAP based on one attribute.
|
||||
*
|
||||
* @param non-empty-string $attribute
|
||||
* @param non-empty-string $searchTerm
|
||||
* @return IUser|null Returns a IUser if found in LDAP using the configured attribute and search term.
|
||||
* @throws MultipleUsersReturnedException If multiple users have been found. The search attribute/term should not allow this.
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function findOneUserByAttributeValue(string $attribute, string $searchTerm): ?IUser;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue