diff --git a/apps/user_ldap/lib/Command/CheckGroup.php b/apps/user_ldap/lib/Command/CheckGroup.php new file mode 100644 index 0000000000000..d15957fd6146a --- /dev/null +++ b/apps/user_ldap/lib/Command/CheckGroup.php @@ -0,0 +1,146 @@ + + * @author Christoph Wurst + * @author Côme Chilliet + * @author Joas Schilling + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\User_LDAP\Command; + +use OCA\User_LDAP\Helper; +use OCA\User_LDAP\Mapping\UserMapping; +use OCA\User_LDAP\Group_Proxy; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class CheckGroup extends Command { + public function __construct( + protected Group_Proxy $backend, + protected Helper $helper, + protected UserMapping $mapping + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('ldap:check-group') + ->setDescription('checks whether a group exists on LDAP.') + ->addArgument( + 'ocName', + InputArgument::REQUIRED, + 'the group name as used in Nextcloud, or the LDAP DN' + ) + ->addOption( + 'force', + null, + InputOption::VALUE_NONE, + 'ignores disabled LDAP configuration' + ) + ->addOption( + 'update', + null, + InputOption::VALUE_NONE, + 'syncs values from LDAP' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + try { + $this->assertAllowed($input->getOption('force')); + $gid = $input->getArgument('ocName'); + if ($this->backend->getLDAPAccess($gid)->stringResemblesDN($gid)) { + $groupname = $this->backend->dn2GroupName($gid); + if ($groupname !== false) { + $gid = $groupname; + } + } + $wasMapped = $this->groupWasMapped($gid); + $exists = $this->backend->groupExistsOnLDAP($gid, true); + if ($exists === true) { + $output->writeln('The group is still available on LDAP.'); + if ($input->getOption('update')) { + $this->updateGroup($gid, $output); + } + return 0; + } elseif ($wasMapped) { + $output->writeln('The group does not exists on LDAP anymore.'); + return 0; + } else { + throw new \Exception('The given group is not a recognized LDAP group.'); + } + } catch (\Exception $e) { + $output->writeln('' . $e->getMessage(). ''); + return 1; + } + } + + /** + * checks whether a group is actually mapped + * @param string $ocName the groupname as used in Nextcloud + */ + protected function groupWasMapped(string $ocName): bool { + $dn = $this->mapping->getDNByName($ocName); + return $dn !== false; + } + + /** + * checks whether the setup allows reliable checking of LDAP group existence + * @throws \Exception + */ + protected function assertAllowed(bool $force): void { + if ($this->helper->haveDisabledConfigurations() && !$force) { + throw new \Exception('Cannot check group existence, because ' + . 'disabled LDAP configurations are present.'); + } + + // we don't check ldapUserCleanupInterval from config.php because this + // action is triggered manually, while the setting only controls the + // background job. + } + + private function updateGroup(string $gid, OutputInterface $output): void { + try { + $access = $this->backend->getLDAPAccess($gid); + $attrs = $access->userManager->getAttributes(); + $user = $access->userManager->get($gid); + $avatarAttributes = $access->getConnection()->resolveRule('avatar'); + $result = $access->search('objectclass=*', $user->getDN(), $attrs, 1, 0); + foreach ($result[0] as $attribute => $valueSet) { + $output->writeln(' ' . $attribute . ': '); + foreach ($valueSet as $value) { + if (in_array($attribute, $avatarAttributes)) { + $value = '{ImageData}'; + } + $output->writeln(' ' . $value); + } + } + $access->batchApplyUserAttributes($result); + } catch (\Exception $e) { + $output->writeln('Error while trying to lookup and update attributes from LDAP'); + } + } +} diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 84267171d3749..3a17ba70bb1a8 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -1105,31 +1105,43 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { * @throws ServerNotAvailableException */ public function groupExists($gid) { - $groupExists = $this->access->connection->getFromCache('groupExists' . $gid); - if (!is_null($groupExists)) { - return (bool)$groupExists; + return $this->groupExistsOnLDAP($gid, false); + } + + /** + * Check if a group exists + * + * @throws ServerNotAvailableException + */ + public function groupExistsOnLDAP(string $gid, bool $ignoreCache = false): bool { + $cacheKey = 'groupExists' . $gid; + if (!$ignoreCache) { + $groupExists = $this->access->connection->getFromCache($cacheKey); + if (!is_null($groupExists)) { + return (bool)$groupExists; + } } //getting dn, if false the group does not exist. If dn, it may be mapped //only, requires more checking. $dn = $this->access->groupname2dn($gid); if (!$dn) { - $this->access->connection->writeToCache('groupExists' . $gid, false); + $this->access->connection->writeToCache($cacheKey, false); return false; } if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) { - $this->access->connection->writeToCache('groupExists' . $gid, false); + $this->access->connection->writeToCache($cacheKey, false); return false; } //if group really still exists, we will be able to read its objectClass if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) { - $this->access->connection->writeToCache('groupExists' . $gid, false); + $this->access->connection->writeToCache($cacheKey, false); return false; } - $this->access->connection->writeToCache('groupExists' . $gid, true); + $this->access->connection->writeToCache($cacheKey, true); return true; } @@ -1336,4 +1348,11 @@ public function getDisplayName(string $gid): string { $this->access->connection->writeToCache($cacheKey, $displayName); return $displayName; } + + /** + * returns the groupname for the given LDAP DN, if available + */ + public function dn2GroupName(string $dn): string|false { + return $this->access->dn2groupname($dn); + } } diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index 5f8d0562fd918..b95486bbf7435 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -286,6 +286,23 @@ public function groupExists($gid) { return $this->handleRequest($gid, 'groupExists', [$gid]); } + /** + * Check if a group exists + * + * @throws ServerNotAvailableException + */ + public function groupExistsOnLDAP(string $gid, bool $ignoreCache = false): bool { + return $this->handleRequest($gid, 'groupExistsOnLDAP', [$gid, $ignoreCache]); + } + + /** + * returns the groupname for the given LDAP DN, if available + */ + public function dn2GroupName(string $dn): string|false { + $id = 'DN,' . $dn; + return $this->handleRequest($id, 'dn2GroupName', [$dn]); + } + /** * Check if backend implements actions *