Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions apps/user_ldap/lib/IUserLDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/
namespace OCA\User_LDAP;

use OCP\LDAP\Exceptions\MultipleUsersReturnedException;

interface IUserLDAP {

//Functions used by LDAPProvider
Expand All @@ -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;
}
13 changes: 11 additions & 2 deletions apps/user_ldap/lib/LDAPProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,15 +30,15 @@ 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,
private LoggerInterface $logger,
) {
$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;
Expand Down Expand Up @@ -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);
}
}
31 changes: 27 additions & 4 deletions apps/user_ldap/lib/User_LDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should $attribute be escaped or validated 🤔

$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;
}
}
27 changes: 22 additions & 5 deletions apps/user_ldap/lib/User_Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,29 @@
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;
use OCP\User\Backend\ILimitAwareCountUsersBackend;
use OCP\User\Backend\IPropertyPermissionBackend;
use OCP\User\Backend\IProvideEnabledStateBackend;
use OCP\UserInterface;
use Override;
use Psr\Log\LoggerInterface;

/**
* @template-extends Proxy<User_LDAP>
*/
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);
}
Expand Down Expand Up @@ -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;
}
}
20 changes: 20 additions & 0 deletions lib/public/LDAP/Exceptions/MultipleUsersReturnedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strict types missing

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 {
}
13 changes: 13 additions & 0 deletions lib/public/LDAP/ILDAPProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

use LDAP\Connection;
use OCP\AppFramework\Attribute\Consumable;
use OCP\IUser;
use OCP\LDAP\Exceptions\MultipleUsersReturnedException;

/**
* Interface ILDAPProvider
Expand Down Expand Up @@ -154,4 +156,15 @@ public function getUserAttribute(string $uid, string $attribute): ?string;
* @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