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
2 changes: 2 additions & 0 deletions apps/user_ldap/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
'OCA\\User_LDAP\\Configuration' => $baseDir . '/../lib/Configuration.php',
'OCA\\User_LDAP\\Connection' => $baseDir . '/../lib/Connection.php',
'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php',
'OCA\\User_LDAP\\Controller\\APIController' => $baseDir . '/../lib/Controller/APIController.php',
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php',
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php',
'OCA\\User_LDAP\\Controller\\WizardController' => $baseDir . '/../lib/Controller/WizardController.php',
Expand Down Expand Up @@ -83,6 +84,7 @@
'OCA\\User_LDAP\\PagedResults\\TLinkId' => $baseDir . '/../lib/PagedResults/TLinkId.php',
'OCA\\User_LDAP\\Proxy' => $baseDir . '/../lib/Proxy.php',
'OCA\\User_LDAP\\Service\\BirthdateParserService' => $baseDir . '/../lib/Service/BirthdateParserService.php',
'OCA\\User_LDAP\\Service\\CheckUserService' => $baseDir . '/../lib/Service/CheckUserService.php',
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => $baseDir . '/../lib/Service/UpdateGroupsService.php',
'OCA\\User_LDAP\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/user_ldap/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ComposerStaticInitUser_LDAP
'OCA\\User_LDAP\\Configuration' => __DIR__ . '/..' . '/../lib/Configuration.php',
'OCA\\User_LDAP\\Connection' => __DIR__ . '/..' . '/../lib/Connection.php',
'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php',
'OCA\\User_LDAP\\Controller\\APIController' => __DIR__ . '/..' . '/../lib/Controller/APIController.php',
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php',
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php',
'OCA\\User_LDAP\\Controller\\WizardController' => __DIR__ . '/..' . '/../lib/Controller/WizardController.php',
Expand Down Expand Up @@ -98,6 +99,7 @@ class ComposerStaticInitUser_LDAP
'OCA\\User_LDAP\\PagedResults\\TLinkId' => __DIR__ . '/..' . '/../lib/PagedResults/TLinkId.php',
'OCA\\User_LDAP\\Proxy' => __DIR__ . '/..' . '/../lib/Proxy.php',
'OCA\\User_LDAP\\Service\\BirthdateParserService' => __DIR__ . '/..' . '/../lib/Service/BirthdateParserService.php',
'OCA\\User_LDAP\\Service\\CheckUserService' => __DIR__ . '/..' . '/../lib/Service/CheckUserService.php',
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => __DIR__ . '/..' . '/../lib/Service/UpdateGroupsService.php',
'OCA\\User_LDAP\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',
Expand Down
93 changes: 16 additions & 77 deletions apps/user_ldap/lib/Command/CheckUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@

namespace OCA\User_LDAP\Command;

use OCA\User_LDAP\Helper;
use OCA\User_LDAP\Mapping\UserMapping;
use OCA\User_LDAP\User\DeletedUsersIndex;
use OCA\User_LDAP\User_Proxy;
use OCA\User_LDAP\Service\CheckUserService;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -21,11 +18,8 @@

class CheckUser extends Command {
public function __construct(
protected User_Proxy $backend,
protected Helper $helper,
protected DeletedUsersIndex $dui,
protected UserMapping $mapping,
protected IUserManager $userManager,
private readonly IUserManager $userManager,
private readonly CheckUserService $checkUserService,
) {
parent::__construct();
}
Expand Down Expand Up @@ -77,7 +71,11 @@ protected function configure(): void {
#[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$this->assertAllowed($input->getOption('force'));
if (!$this->checkUserService->assertAllowed($input->getOption('force'))) {
throw new \Exception('Cannot check user existence, because '
. 'disabled LDAP configurations are present.');
}

$uid = $input->getArgument('ocName');

if ($uid !== null) {
Expand Down Expand Up @@ -109,77 +107,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

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) {
$result = $this->checkUserService->checkUser($uid, $input->getOption('update'));
if ($result['exists']) {
$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);
} elseif ($result['wasMapped']) {
$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
*/
protected function userWasMapped(string $ocName): bool {
$dn = $this->mapping->getDNByName($ocName);
return $dn !== false;
}

/**
* checks whether the setup allows reliable checking of LDAP user existence
* @throws \Exception
*/
protected function assertAllowed(bool $force): void {
if ($this->helper->haveDisabledConfigurations() && !$force) {
throw new \Exception('Cannot check user existence, because '
. 'disabled LDAP configurations are present.');
$output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' . $uid . '"');
}

// we don't check ldapUserCleanupInterval from config.php because this
// action is triggered manually, while the setting only controls the
// background job.
}

private function updateUser(string $uid, OutputInterface $output): void {
try {
$access = $this->backend->getLDAPAccess($uid);
$attrs = $access->userManager->getAttributes();
$user = $access->userManager->get($uid);
$avatarAttributes = $access->getConnection()->resolveRule('avatar');
$baseDn = $this->helper->DNasBaseParameter($user->getDN());
$result = $access->search('objectclass=*', $baseDn, $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);
}
if (isset($result['attributes'])) {
foreach ($result['attributes'] as $attribute => $value) {
$output->writeln(' ' . $attribute . ': ' . $value);
}
$access->batchApplyUserAttributes($result);
} catch (\Exception $e) {
$output->writeln('<error>Error while trying to lookup and update attributes from LDAP</error>');
}
return self::SUCCESS;
}
}
55 changes: 55 additions & 0 deletions apps/user_ldap/lib/Controller/APIController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\User_LDAP\Controller;

use OCA\User_LDAP\Configuration;
use OCA\User_LDAP\LDAP;
use OCA\User_LDAP\Service\CheckUserService;
use OCA\User_LDAP\Settings\Admin;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Server;

class APIController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private readonly CheckUserService $checkUserService,
) {
parent::__construct($appName, $request);
}

/**
* Check a user and update its attribute in the LDAP server
*
* @param string $userID ID of the user
* @return DataResponse<Http::STATUS_OK, array{exists:bool, wasMapped: bool, attributes?: array<string, string>}, array{}>
* @throws OCSException An unexpected error happened
* @throws OCSForbiddenException Cannot check user existence
*
* 200: The user configuration as found in the LDAP server
*/
#[ApiRoute(verb: 'POST', url: '/api/v1/checkUser/{userID}')]
#[AuthorizedAdminSetting(settings: Admin::class)]
public function checkUser(string $userID): DataResponse {
if ($this->checkUserService->assertAllowed(false)) {
throw new OCSForbiddenException('Cannot check user existence, because disabled LDAP configurations are present.');
}

$result = $this->checkUserService->checkUser($userID, true);
return new DataResponse($result);
}
}
5 changes: 2 additions & 3 deletions apps/user_ldap/lib/Controller/ConfigAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ public function testConfiguration(string $configID) {
*/
#[AuthorizedAdminSetting(settings: Admin::class)]
#[ApiRoute(verb: 'POST', url: '/api/v1/config/{configID}/copy')]
public function copyConfiguration(string $configID) {
public function copyConfiguration(string $configID): DataResponse {
try {
$this->ensureConfigIDExists($configID);
$configPrefix = $this->ldapHelper->getNextServerConfigurationPrefix();
Expand All @@ -337,8 +337,7 @@ public function copyConfiguration(string $configID) {
* @param string $configID
* @throws OCSNotFoundException
*/
#[AuthorizedAdminSetting(settings: Admin::class)]
private function ensureConfigIDExists($configID): void {
private function ensureConfigIDExists(string $configID): void {
$prefixes = $this->ldapHelper->getServerConfigurationPrefixes();
if (!in_array($configID, $prefixes, true)) {
throw new OCSNotFoundException('Config ID not found');
Expand Down
106 changes: 106 additions & 0 deletions apps/user_ldap/lib/Service/CheckUserService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OCA\User_LDAP\Service;

use OCA\User_LDAP\Helper;
use OCA\User_LDAP\Mapping\UserMapping;
use OCA\User_LDAP\User\DeletedUsersIndex;
use OCA\User_LDAP\User_Proxy;
use OCP\IUserManager;
use OCP\User\Exceptions\UserNotFoundException;

class CheckUserService {
public function __construct(
protected readonly User_Proxy $backend,
protected readonly Helper $helper,
protected readonly DeletedUsersIndex $dui,
protected readonly UserMapping $mapping,
protected readonly IUserManager $userManager,
) {
}

/**
* @param string $uid
* @param bool $update
* @return array{exists: true, wasMapped: true, attributes?: array<string, string>}

Check failure on line 31 in apps/user_ldap/lib/Service/CheckUserService.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

MismatchingDocblockReturnType

apps/user_ldap/lib/Service/CheckUserService.php:31:13: MismatchingDocblockReturnType: Docblock has incorrect return type 'array{attributes?: array<string, string>, exists: true, wasMapped: true}', should be 'int' (see https://psalm.dev/142)
* @throws UserNotFoundException
* @throws \OCP\PreConditionNotMetException
*/
public function checkUser(string $uid, bool $update): 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) {
if ($update) {
$attributes = $this->updateUser($uid);
return ['exists' => true, 'wasMapped' => $wasMapped, 'attributes' => $attributes];
}
return ['exists' => true, 'wasMapped' => $wasMapped];
}

if (!$wasMapped) {
throw new UserNotFoundException('The given user is not a recognized LDAP user.');
}

$this->dui->markUser($uid);
return ['exists' => true, 'wasMapped' => $wasMapped];
}

/**
* checks whether a user is actually mapped
* @param string $ocName the username as used in Nextcloud
*/
protected function userWasMapped(string $ocName): bool {
$dn = $this->mapping->getDNByName($ocName);
return $dn !== false;
}

/**
* Checks whether the setup allows reliable checking of LDAP user existence
*/
public function assertAllowed(bool $force): bool {
// we don't check ldapUserCleanupInterval from config.php because this
// action is triggered manually, while the setting only controls the
// background job.
return !$this->helper->haveDisabledConfigurations() || $force;
}

/**
* @param string $uid
* @return array<string, string> The attributes
*/
private function updateUser(string $uid): array {
try {
$access = $this->backend->getLDAPAccess($uid);
$attrs = $access->userManager->getAttributes();
$user = $access->userManager->get($uid);
$avatarAttributes = $access->getConnection()->resolveRule('avatar');
$baseDn = $this->helper->DNasBaseParameter($user->getDN());
$result = $access->search('objectclass=*', $baseDn, $attrs, 1, 0);
$attributes = [];
foreach ($result[0] as $attribute => $valueSet) {
foreach ($valueSet as $value) {
if (in_array($attribute, $avatarAttributes)) {
$value = '{ImageData}';
}
$attributes[$attribute] = $value;
}
}
$access->batchApplyUserAttributes($result);
return $attributes;
} catch (\Exception $e) {
throw new \Exception('Error while trying to lookup and update attributes from LDAP', previous: $e);
}
}
}
Loading
Loading