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
26 changes: 22 additions & 4 deletions apps/user_ldap/lib/Access.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function __construct(
private Helper $helper,
private IConfig $config,
private IUserManager $ncUserManager,
private IGroupManager $ncGroupManager,
private LoggerInterface $logger,
private IAppConfig $appConfig,
private IEventDispatcher $dispatcher,
Expand Down Expand Up @@ -595,18 +596,35 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
$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'
Comment on lines +602 to +605
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not understand the getNameByDN part of the comment.
I do not see what prevents collisions with this PR.
If I set username to a non-unique LDAP attribute, with this PR I will run into trouble no?

Would be way cleaner to either clear on ignore the cache in this code path.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this is the problem, it relies on certain behavior inside the Mappings class. More later.

If I set username to a non-unique LDAP attribute, with this PR I will run into trouble no?

No, I don't see why you would.

Would be way cleaner to either clear on ignore the cache in this code path.

I don't want to clear the whole cache, this is pointless for an edge case while the mechanism as a whole is quite effective!

The memcache uses the uid as key (we don't have it) and is not really searchable. So without making the structure bigger and more redundant, which should be avoided, we can make the mapper more complicated:

  • remember all DNs that do not resolve in a name (runtime only)
  • if we encounter a cache hit involving such a DN, reset the cache entry and discard the value

What I don't like about this approach is that cache logic and mechanism are spread along the mapping. But it is better than the current approach, which relies on certain behaviour in different classes (this gives me the stomach aches tbh).

The good thing is that wrong values don't hurt, they'll be repaired (as long as the cached DN is not-existing that is).

Copy link
Contributor

Choose a reason for hiding this comment

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

If I set username to a non-unique LDAP attribute, with this PR I will run into trouble no?

No, I don't see why you would.

To me it looks like this change defeats the collision detection mechanism.
If a user already exists with the same uid, it will happily return it even if it’s not the same one. Or did I miss where there is a check that the existing user and the new one are the same?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not that still works:

Screenshot_20251124_190822

Also works through web interface (with memcache in action).

Reason is that mapAndAnnounceIfApplicable() will return false due to database constraints (cf. AbstractMapping::map()).

Copy link
Member Author

Choose a reason for hiding this comment

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

Nevertheless, would still tend to move it into the mapping. I don't like the reliance on inner workings of other modules, as stated in #56292 (comment).

))
|| (!$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';

Comment on lines +625 to +627
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
$dude = $this->ncUserManager->get($intName);
$dude->getBackendClassName() === 'LDAP';

left-over?

Copy link
Member Author

Choose a reason for hiding this comment

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

oops 🙂 yes

$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
if (is_string($altName)) {
Expand Down Expand Up @@ -830,7 +848,7 @@ private function _createAltInternalOwnCloudNameForGroups(string $name) {
// 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);
Expand Down
3 changes: 3 additions & 0 deletions apps/user_ldap/lib/AccessFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Server;
use Psr\Log\LoggerInterface;
Expand All @@ -22,6 +23,7 @@ public function __construct(
private IConfig $config,
private IAppConfig $appConfig,
private IUserManager $ncUserManager,
private IGroupManager $ncGroupManager,
private LoggerInterface $logger,
private IEventDispatcher $dispatcher,
) {
Expand All @@ -36,6 +38,7 @@ public function get(Connection $connection): Access {
$this->helper,
$this->config,
$this->ncUserManager,
$this->ncGroupManager,
$this->logger,
$this->appConfig,
$this->dispatcher,
Expand Down
10 changes: 7 additions & 3 deletions apps/user_ldap/tests/AccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCP\IAppConfig;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\Image;
use OCP\IUserManager;
use OCP\Notification\IManager as INotificationManager;
Expand All @@ -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;
Expand All @@ -67,6 +69,7 @@ protected function setUp(): void {
$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);
Expand All @@ -79,6 +82,7 @@ protected function setUp(): void {
$this->helper,
$this->config,
$this->ncUserManager,
$this->ncGroupManager,
$this->logger,
$this->appConfig,
$this->dispatcher,
Expand Down Expand Up @@ -224,7 +228,7 @@ public function testStringResemblesDN(string $input, array|bool $interResult, bo
[$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')
Expand All @@ -244,7 +248,7 @@ public function testStringResemblesDNLDAPmod(string $input, array|bool $interRes
/** @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');
Expand Down Expand Up @@ -427,7 +431,7 @@ public function testSanitizeDN(string $attribute): void {
$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));
}
Expand Down
Loading