From 0bcf760ac1760905270d8c381eaf5420834694fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Apr 2026 14:51:28 +0200 Subject: [PATCH 1/2] feat(user_ldap): Add option to check all seen users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can be useful in some situations to sync all seen users with --update Signed-off-by: Côme Chilliet --- apps/user_ldap/lib/Command/CheckUser.php | 79 +++++++++++++++++------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/apps/user_ldap/lib/Command/CheckUser.php b/apps/user_ldap/lib/Command/CheckUser.php index 75c13f71b7d2f..9d67dd7fe990a 100644 --- a/apps/user_ldap/lib/Command/CheckUser.php +++ b/apps/user_ldap/lib/Command/CheckUser.php @@ -11,6 +11,8 @@ use OCA\User_LDAP\Mapping\UserMapping; use OCA\User_LDAP\User\DeletedUsersIndex; use OCA\User_LDAP\User_Proxy; +use OCP\IUser; +use OCP\IUserManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -23,6 +25,7 @@ public function __construct( protected Helper $helper, protected DeletedUsersIndex $dui, protected UserMapping $mapping, + protected IUserManager $userManager, ) { parent::__construct(); } @@ -34,7 +37,7 @@ protected function configure(): void { ->setDescription('checks whether a user exists on LDAP.') ->addArgument( 'ocName', - InputArgument::REQUIRED, + InputArgument::OPTIONAL, 'the user name as used in Nextcloud, or the LDAP DN' ) ->addOption( @@ -49,6 +52,12 @@ protected function configure(): void { InputOption::VALUE_NONE, 'syncs values from LDAP' ) + ->addOption( + 'all-seen-users', + null, + InputOption::VALUE_NONE, + 'sync all seen users instead of only one' + ) ; } @@ -57,37 +66,61 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $this->assertAllowed($input->getOption('force')); $uid = $input->getArgument('ocName'); - if ($this->backend->getLDAPAccess($uid)->stringResemblesDN($uid)) { - $username = $this->backend->dn2UserName($uid); - if ($username !== false) { - $uid = $username; - } - } - $wasMapped = $this->userWasMapped($uid); - $exists = $this->backend->userExistsOnLDAP($uid, true); - if ($exists === true) { - $output->writeln('The user is still available on LDAP.'); - if ($input->getOption('update')) { - $this->updateUser($uid, $output); - } - return self::SUCCESS; - } - if ($wasMapped) { - $this->dui->markUser($uid); - $output->writeln('The user does not exists on LDAP anymore.'); - $output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' - . $uid . '"'); + if ($uid !== null) { + return $this->checkUser($input, $output, $uid); + } elseif ($input->getOption('all-seen-users')) { + $this->userManager->callForSeenUsers( + function (IUser $user) use ($input, $output): true { + try { + $output->writeln('Checking ' . $user->getUID() . '…', OutputInterface::VERBOSITY_VERBOSE); + $this->checkUser($input, $output, $user->getUID()); + } catch (\Exception $e) { + $output->writeln(' ' . $user->getUID() . ': ' . $e->getMessage() . ''); + } + /* Always continue */ + return true; + } + ); + $output->writeln('Finished checking all seen users.', OutputInterface::VERBOSITY_VERBOSE); return self::SUCCESS; + } else { + throw new \InvalidArgumentException('Either a user name or --all-seen-users is required'); } - - throw new \Exception('The given user is not a recognized LDAP user.'); } catch (\Exception $e) { $output->writeln('' . $e->getMessage() . ''); return self::FAILURE; } } + private function checkUser(InputInterface $input, OutputInterface $output, string $uid): int { + if ($this->backend->getLDAPAccess($uid)->stringResemblesDN($uid)) { + $username = $this->backend->dn2UserName($uid); + if ($username !== false) { + $uid = $username; + } + } + $wasMapped = $this->userWasMapped($uid); + $exists = $this->backend->userExistsOnLDAP($uid, true); + if ($exists === true) { + $output->writeln('The user is still available on LDAP.'); + if ($input->getOption('update')) { + $this->updateUser($uid, $output); + } + return self::SUCCESS; + } + + if ($wasMapped) { + $this->dui->markUser($uid); + $output->writeln('The user does not exists on LDAP anymore.'); + $output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' + . $uid . '"'); + return self::SUCCESS; + } + + throw new \Exception('The given user is not a recognized LDAP user.'); + } + /** * checks whether a user is actually mapped * @param string $ocName the username as used in Nextcloud From 7bd0050564900893d573feb80048e769061d258b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 30 Apr 2026 14:44:20 +0200 Subject: [PATCH 2/2] feat(user_ldap): Add a limit and offset options for check-user --all-seen-users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/user_ldap/lib/Command/CheckUser.php | 38 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/apps/user_ldap/lib/Command/CheckUser.php b/apps/user_ldap/lib/Command/CheckUser.php index 9d67dd7fe990a..0c482c191879f 100644 --- a/apps/user_ldap/lib/Command/CheckUser.php +++ b/apps/user_ldap/lib/Command/CheckUser.php @@ -11,7 +11,6 @@ use OCA\User_LDAP\Mapping\UserMapping; use OCA\User_LDAP\User\DeletedUsersIndex; use OCA\User_LDAP\User_Proxy; -use OCP\IUser; use OCP\IUserManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -58,6 +57,19 @@ protected function configure(): void { InputOption::VALUE_NONE, 'sync all seen users instead of only one' ) + ->addOption( + 'limit', + null, + InputOption::VALUE_REQUIRED, + 'limit the number of user to process for --all-seen-users' + ) + ->addOption( + 'offset', + null, + InputOption::VALUE_REQUIRED, + 'offset to apply for --all-seen-users', + 0 + ) ; } @@ -70,18 +82,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($uid !== null) { return $this->checkUser($input, $output, $uid); } elseif ($input->getOption('all-seen-users')) { - $this->userManager->callForSeenUsers( - function (IUser $user) use ($input, $output): true { - try { - $output->writeln('Checking ' . $user->getUID() . '…', OutputInterface::VERBOSITY_VERBOSE); - $this->checkUser($input, $output, $user->getUID()); - } catch (\Exception $e) { - $output->writeln(' ' . $user->getUID() . ': ' . $e->getMessage() . ''); - } - /* Always continue */ - return true; + $offset = (int)$input->getOption('offset'); + $limit = $input->getOption('limit'); + if ($limit !== null) { + $limit = (int)$limit; + } + $userIterator = $this->userManager->getSeenUsers($offset, $limit); + foreach ($userIterator as $user) { + try { + $output->writeln('Checking ' . $user->getUID() . '…', OutputInterface::VERBOSITY_VERBOSE); + $this->checkUser($input, $output, $user->getUID()); + } catch (\Exception $e) { + $output->writeln(' ' . $user->getUID() . ': ' . $e->getMessage() . ''); } - ); + } $output->writeln('Finished checking all seen users.', OutputInterface::VERBOSITY_VERBOSE); return self::SUCCESS; } else {