fix(LDAP): unblock a cached id from a deleted user or group

As of 155b75027c we cache the user id and
LDAP DN for a month, because they are highly requested, but rarely change
and we have a working repair mechanism.

But if an LDAP user is deleted, the information is still cached. So a user
that is being regenerated with the same target user ID will get a _xxxx
suffix, when the data is still in local cache.

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
This commit is contained in:
Arthur Schiwon 2025-11-07 20:32:00 +01:00
parent d14cf6a8be
commit 5ce8e06add
No known key found for this signature in database
GPG key ID: 7424F1874854DF23
3 changed files with 32 additions and 7 deletions

View file

@ -57,6 +57,7 @@ class Access extends LDAPUtility {
private Helper $helper,
private IConfig $config,
private IUserManager $ncUserManager,
private IGroupManager $ncGroupManager,
private LoggerInterface $logger,
private IAppConfig $appConfig,
private IEventDispatcher $dispatcher,
@ -595,18 +596,35 @@ class Access extends LDAPUtility {
$originalTTL = $this->connection->ldapCacheTTL;
$this->connection->setConfiguration(['ldapCacheTTL' => 0]);
if ($intName !== ''
&& (($isUser && !$this->ncUserManager->userExists($intName))
|| (!$isUser && !Server::get(IGroupManager::class)->groupExists($intName))
&& (($isUser
&& (
!$this->ncUserManager->userExists($intName)
// DN might be cached if the user was deleted within last month.
// If it was a valid user we would have returned earlier at the
// $mapper->getNameByDN($fdn) call, which does not run against the cache.
|| $this->ncUserManager->get($intName)?->getBackendClassName() === 'LDAP'
))
|| (!$isUser
&& (
// DN might be cached if the group was deleted within last month.
// If it was a valid group we would have returned earlier at the
// $mapper->getNameByDN($fdn) call, which does not run against the cache.
!$this->ncGroupManager->groupExists($intName)
|| in_array('LDAP', $this->ncGroupManager->get($intName)?->getBackendNames() ?? [], true)
))
)
) {
$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]);
$this->logger->debug('Mapped {fdn} as {name}', ['fdn' => $fdn, 'name' => $intName]);
return $intName;
}
}
$dude = $this->ncUserManager->get($intName);
$dude->getBackendClassName() === 'LDAP';
$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
if (is_string($altName)) {
@ -830,7 +848,7 @@ class Access extends LDAPUtility {
// Check to be really sure it is unique
// while loop is just a precaution. If a name is not generated within
// 20 attempts, something else is very wrong. Avoids infinite loop.
if (!Server::get(IGroupManager::class)->groupExists($altName)) {
if (!$this->ncGroupManager->groupExists($altName)) {
return $altName;
}
$altName = $name . '_' . ($lastNo + $attempts);

View file

@ -10,6 +10,7 @@ use OCA\User_LDAP\User\Manager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Server;
use Psr\Log\LoggerInterface;
@ -22,6 +23,7 @@ class AccessFactory {
private IConfig $config,
private IAppConfig $appConfig,
private IUserManager $ncUserManager,
private IGroupManager $ncGroupManager,
private LoggerInterface $logger,
private IEventDispatcher $dispatcher,
) {
@ -36,6 +38,7 @@ class AccessFactory {
$this->helper,
$this->config,
$this->ncUserManager,
$this->ncGroupManager,
$this->logger,
$this->appConfig,
$this->dispatcher,

View file

@ -25,6 +25,7 @@ use OCP\HintException;
use OCP\IAppConfig;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\Image;
use OCP\IUserManager;
use OCP\Notification\IManager as INotificationManager;
@ -51,6 +52,7 @@ class AccessTest extends TestCase {
private Helper&MockObject $helper;
private IConfig&MockObject $config;
private IUserManager&MockObject $ncUserManager;
private IGroupManager&MockObject $ncGroupManager;
private LoggerInterface&MockObject $logger;
private IAppConfig&MockObject $appConfig;
private IEventDispatcher&MockObject $dispatcher;
@ -67,6 +69,7 @@ class AccessTest extends TestCase {
$this->userMapper = $this->createMock(UserMapping::class);
$this->groupMapper = $this->createMock(GroupMapping::class);
$this->ncUserManager = $this->createMock(IUserManager::class);
$this->ncGroupManager = $this->createMock(IGroupManager::class);
$this->shareManager = $this->createMock(IManager::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->appConfig = $this->createMock(IAppConfig::class);
@ -79,6 +82,7 @@ class AccessTest extends TestCase {
$this->helper,
$this->config,
$this->ncUserManager,
$this->ncGroupManager,
$this->logger,
$this->appConfig,
$this->dispatcher,
@ -224,7 +228,7 @@ class AccessTest extends TestCase {
[$lw, $con, $um, $helper] = $this->getConnectorAndLdapMock();
/** @var IConfig&MockObject $config */
$config = $this->createMock(IConfig::class);
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->ncGroupManager, $this->logger, $this->appConfig, $this->dispatcher);
$lw->expects($this->exactly(1))
->method('explodeDN')
@ -244,7 +248,7 @@ class AccessTest extends TestCase {
/** @var IConfig&MockObject $config */
$config = $this->createMock(IConfig::class);
$lw = new LDAP();
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->ncGroupManager, $this->logger, $this->appConfig, $this->dispatcher);
if (!function_exists('ldap_explode_dn')) {
$this->markTestSkipped('LDAP Module not available');
@ -427,7 +431,7 @@ class AccessTest extends TestCase {
$attribute => ['count' => 1, $dnFromServer]
]);
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->ncGroupManager, $this->logger, $this->appConfig, $this->dispatcher);
$values = $access->readAttribute('uid=whoever,dc=example,dc=org', $attribute);
$this->assertSame($values[0], strtolower($dnFromServer));
}