Skip to content

Commit a58ec01

Browse files
committed
feat: Provide check user as an OCS API
Refactor the command in a service and call the same code in a new controller as well as the existing occ command. Signed-off-by: Carl Schwan <carlschwan@kde.org>
1 parent 21fbfa1 commit a58ec01

8 files changed

Lines changed: 523 additions & 80 deletions

File tree

apps/user_ldap/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'OCA\\User_LDAP\\Configuration' => $baseDir . '/../lib/Configuration.php',
2929
'OCA\\User_LDAP\\Connection' => $baseDir . '/../lib/Connection.php',
3030
'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php',
31+
'OCA\\User_LDAP\\Controller\\APIController' => $baseDir . '/../lib/Controller/APIController.php',
3132
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php',
3233
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php',
3334
'OCA\\User_LDAP\\Controller\\WizardController' => $baseDir . '/../lib/Controller/WizardController.php',
@@ -83,6 +84,7 @@
8384
'OCA\\User_LDAP\\PagedResults\\TLinkId' => $baseDir . '/../lib/PagedResults/TLinkId.php',
8485
'OCA\\User_LDAP\\Proxy' => $baseDir . '/../lib/Proxy.php',
8586
'OCA\\User_LDAP\\Service\\BirthdateParserService' => $baseDir . '/../lib/Service/BirthdateParserService.php',
87+
'OCA\\User_LDAP\\Service\\CheckUserService' => $baseDir . '/../lib/Service/CheckUserService.php',
8688
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => $baseDir . '/../lib/Service/UpdateGroupsService.php',
8789
'OCA\\User_LDAP\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
8890
'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',

apps/user_ldap/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class ComposerStaticInitUser_LDAP
4343
'OCA\\User_LDAP\\Configuration' => __DIR__ . '/..' . '/../lib/Configuration.php',
4444
'OCA\\User_LDAP\\Connection' => __DIR__ . '/..' . '/../lib/Connection.php',
4545
'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php',
46+
'OCA\\User_LDAP\\Controller\\APIController' => __DIR__ . '/..' . '/../lib/Controller/APIController.php',
4647
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php',
4748
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php',
4849
'OCA\\User_LDAP\\Controller\\WizardController' => __DIR__ . '/..' . '/../lib/Controller/WizardController.php',
@@ -98,6 +99,7 @@ class ComposerStaticInitUser_LDAP
9899
'OCA\\User_LDAP\\PagedResults\\TLinkId' => __DIR__ . '/..' . '/../lib/PagedResults/TLinkId.php',
99100
'OCA\\User_LDAP\\Proxy' => __DIR__ . '/..' . '/../lib/Proxy.php',
100101
'OCA\\User_LDAP\\Service\\BirthdateParserService' => __DIR__ . '/..' . '/../lib/Service/BirthdateParserService.php',
102+
'OCA\\User_LDAP\\Service\\CheckUserService' => __DIR__ . '/..' . '/../lib/Service/CheckUserService.php',
101103
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => __DIR__ . '/..' . '/../lib/Service/UpdateGroupsService.php',
102104
'OCA\\User_LDAP\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
103105
'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',

apps/user_ldap/lib/Command/CheckUser.php

Lines changed: 16 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88

99
namespace OCA\User_LDAP\Command;
1010

11-
use OCA\User_LDAP\Helper;
12-
use OCA\User_LDAP\Mapping\UserMapping;
13-
use OCA\User_LDAP\User\DeletedUsersIndex;
14-
use OCA\User_LDAP\User_Proxy;
11+
use OCA\User_LDAP\Service\CheckUserService;
1512
use OCP\IUserManager;
1613
use Symfony\Component\Console\Command\Command;
1714
use Symfony\Component\Console\Input\InputArgument;
@@ -21,11 +18,8 @@
2118

2219
class CheckUser extends Command {
2320
public function __construct(
24-
protected User_Proxy $backend,
25-
protected Helper $helper,
26-
protected DeletedUsersIndex $dui,
27-
protected UserMapping $mapping,
28-
protected IUserManager $userManager,
21+
private readonly IUserManager $userManager,
22+
private readonly CheckUserService $checkUserService,
2923
) {
3024
parent::__construct();
3125
}
@@ -77,7 +71,11 @@ protected function configure(): void {
7771
#[\Override]
7872
protected function execute(InputInterface $input, OutputInterface $output): int {
7973
try {
80-
$this->assertAllowed($input->getOption('force'));
74+
if (!$this->checkUserService->assertAllowed($input->getOption('force'))) {
75+
throw new \Exception('Cannot check user existence, because '
76+
. 'disabled LDAP configurations are present.');
77+
}
78+
8179
$uid = $input->getArgument('ocName');
8280

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

111109
private function checkUser(InputInterface $input, OutputInterface $output, string $uid): int {
112-
if ($this->backend->getLDAPAccess($uid)->stringResemblesDN($uid)) {
113-
$username = $this->backend->dn2UserName($uid);
114-
if ($username !== false) {
115-
$uid = $username;
116-
}
117-
}
118-
$wasMapped = $this->userWasMapped($uid);
119-
$exists = $this->backend->userExistsOnLDAP($uid, true);
120-
if ($exists === true) {
110+
$result = $this->checkUserService->checkUser($uid, $input->getOption('update'));
111+
if ($result['exists']) {
121112
$output->writeln('The user is still available on LDAP.');
122-
if ($input->getOption('update')) {
123-
$this->updateUser($uid, $output);
124-
}
125-
return self::SUCCESS;
126-
}
127-
128-
if ($wasMapped) {
129-
$this->dui->markUser($uid);
113+
} elseif ($result['wasMapped']) {
130114
$output->writeln('The user does not exists on LDAP anymore.');
131-
$output->writeln('Clean up the user\'s remnants by: ./occ user:delete "'
132-
. $uid . '"');
133-
return self::SUCCESS;
134-
}
135-
136-
throw new \Exception('The given user is not a recognized LDAP user.');
137-
}
138-
139-
/**
140-
* checks whether a user is actually mapped
141-
* @param string $ocName the username as used in Nextcloud
142-
*/
143-
protected function userWasMapped(string $ocName): bool {
144-
$dn = $this->mapping->getDNByName($ocName);
145-
return $dn !== false;
146-
}
147-
148-
/**
149-
* checks whether the setup allows reliable checking of LDAP user existence
150-
* @throws \Exception
151-
*/
152-
protected function assertAllowed(bool $force): void {
153-
if ($this->helper->haveDisabledConfigurations() && !$force) {
154-
throw new \Exception('Cannot check user existence, because '
155-
. 'disabled LDAP configurations are present.');
115+
$output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' . $uid . '"');
156116
}
157-
158-
// we don't check ldapUserCleanupInterval from config.php because this
159-
// action is triggered manually, while the setting only controls the
160-
// background job.
161-
}
162-
163-
private function updateUser(string $uid, OutputInterface $output): void {
164-
try {
165-
$access = $this->backend->getLDAPAccess($uid);
166-
$attrs = $access->userManager->getAttributes();
167-
$user = $access->userManager->get($uid);
168-
$avatarAttributes = $access->getConnection()->resolveRule('avatar');
169-
$baseDn = $this->helper->DNasBaseParameter($user->getDN());
170-
$result = $access->search('objectclass=*', $baseDn, $attrs, 1, 0);
171-
foreach ($result[0] as $attribute => $valueSet) {
172-
$output->writeln(' ' . $attribute . ': ');
173-
foreach ($valueSet as $value) {
174-
if (in_array($attribute, $avatarAttributes)) {
175-
$value = '{ImageData}';
176-
}
177-
$output->writeln(' ' . $value);
178-
}
117+
if (isset($result['attributes'])) {
118+
foreach ($result['attributes'] as $attribute => $value) {
119+
$output->writeln(' ' . $attribute . ': ' . $value);
179120
}
180-
$access->batchApplyUserAttributes($result);
181-
} catch (\Exception $e) {
182-
$output->writeln('<error>Error while trying to lookup and update attributes from LDAP</error>');
183121
}
122+
return self::SUCCESS;
184123
}
185124
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\User_LDAP\Controller;
11+
12+
use OCA\User_LDAP\Configuration;
13+
use OCA\User_LDAP\LDAP;
14+
use OCA\User_LDAP\Service\CheckUserService;
15+
use OCA\User_LDAP\Settings\Admin;
16+
use OCP\AppFramework\Http;
17+
use OCP\AppFramework\Http\Attribute\ApiRoute;
18+
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
19+
use OCP\AppFramework\Http\DataResponse;
20+
use OCP\AppFramework\OCS\OCSException;
21+
use OCP\AppFramework\OCS\OCSForbiddenException;
22+
use OCP\AppFramework\OCSController;
23+
use OCP\IRequest;
24+
use OCP\Server;
25+
26+
class APIController extends OCSController {
27+
public function __construct(
28+
string $appName,
29+
IRequest $request,
30+
private readonly CheckUserService $checkUserService,
31+
) {
32+
parent::__construct($appName, $request);
33+
}
34+
35+
/**
36+
* Check a user and update its attribute in the LDAP server
37+
*
38+
* @param string $userID ID of the user
39+
* @return DataResponse<Http::STATUS_OK, array{exists:bool, wasMapped: bool, attributes?: array<string, string>}, array{}>
40+
* @throws OCSException An unexpected error happened
41+
* @throws OCSForbiddenException Cannot check user existence
42+
*
43+
* 200: The user configuration as found in the LDAP server
44+
*/
45+
#[ApiRoute(verb: 'POST', url: '/api/v1/checkUser/{userID}')]
46+
#[AuthorizedAdminSetting(settings: Admin::class)]
47+
public function checkUser(string $userID): DataResponse {
48+
if ($this->checkUserService->assertAllowed(false)) {
49+
throw new OCSForbiddenException('Cannot check user existence, because disabled LDAP configurations are present.');
50+
}
51+
52+
$result = $this->checkUserService->checkUser($userID, true);
53+
return new DataResponse($result);
54+
}
55+
}

apps/user_ldap/lib/Controller/ConfigAPIController.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public function testConfiguration(string $configID) {
314314
*/
315315
#[AuthorizedAdminSetting(settings: Admin::class)]
316316
#[ApiRoute(verb: 'POST', url: '/api/v1/config/{configID}/copy')]
317-
public function copyConfiguration(string $configID) {
317+
public function copyConfiguration(string $configID): DataResponse {
318318
try {
319319
$this->ensureConfigIDExists($configID);
320320
$configPrefix = $this->ldapHelper->getNextServerConfigurationPrefix();
@@ -337,8 +337,7 @@ public function copyConfiguration(string $configID) {
337337
* @param string $configID
338338
* @throws OCSNotFoundException
339339
*/
340-
#[AuthorizedAdminSetting(settings: Admin::class)]
341-
private function ensureConfigIDExists($configID): void {
340+
private function ensureConfigIDExists(string $configID): void {
342341
$prefixes = $this->ldapHelper->getServerConfigurationPrefixes();
343342
if (!in_array($configID, $prefixes, true)) {
344343
throw new OCSNotFoundException('Config ID not found');
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
6+
* SPDX-License-Identifier: AGPL-3.0-only
7+
*/
8+
9+
namespace OCA\User_LDAP\Service;
10+
11+
use OCA\User_LDAP\Helper;
12+
use OCA\User_LDAP\Mapping\UserMapping;
13+
use OCA\User_LDAP\User\DeletedUsersIndex;
14+
use OCA\User_LDAP\User_Proxy;
15+
use OCP\IUserManager;
16+
use OCP\User\Exceptions\UserNotFoundException;
17+
18+
class CheckUserService {
19+
public function __construct(
20+
protected readonly User_Proxy $backend,
21+
protected readonly Helper $helper,
22+
protected readonly DeletedUsersIndex $dui,
23+
protected readonly UserMapping $mapping,
24+
protected readonly IUserManager $userManager,
25+
) {
26+
}
27+
28+
/**
29+
* @param string $uid
30+
* @param bool $update
31+
* @return array{exists: true, wasMapped: true, attributes?: array<string, string>}
32+
* @throws UserNotFoundException
33+
* @throws \OCP\PreConditionNotMetException
34+
*/
35+
public function checkUser(string $uid, bool $update): int {
36+
if ($this->backend->getLDAPAccess($uid)->stringResemblesDN($uid)) {
37+
$username = $this->backend->dn2UserName($uid);
38+
if ($username !== false) {
39+
$uid = $username;
40+
}
41+
}
42+
$wasMapped = $this->userWasMapped($uid);
43+
$exists = $this->backend->userExistsOnLDAP($uid, true);
44+
if ($exists === true) {
45+
if ($update) {
46+
$attributes = $this->updateUser($uid);
47+
return ['exists' => true, 'wasMapped' => $wasMapped, 'attributes' => $attributes];
48+
}
49+
return ['exists' => true, 'wasMapped' => $wasMapped];
50+
}
51+
52+
if (!$wasMapped) {
53+
throw new UserNotFoundException('The given user is not a recognized LDAP user.');
54+
}
55+
56+
$this->dui->markUser($uid);
57+
return ['exists' => true, 'wasMapped' => $wasMapped];
58+
}
59+
60+
/**
61+
* checks whether a user is actually mapped
62+
* @param string $ocName the username as used in Nextcloud
63+
*/
64+
protected function userWasMapped(string $ocName): bool {
65+
$dn = $this->mapping->getDNByName($ocName);
66+
return $dn !== false;
67+
}
68+
69+
/**
70+
* Checks whether the setup allows reliable checking of LDAP user existence
71+
*/
72+
public function assertAllowed(bool $force): bool {
73+
// we don't check ldapUserCleanupInterval from config.php because this
74+
// action is triggered manually, while the setting only controls the
75+
// background job.
76+
return !$this->helper->haveDisabledConfigurations() || $force;
77+
}
78+
79+
/**
80+
* @param string $uid
81+
* @return array<string, string> The attributes
82+
*/
83+
private function updateUser(string $uid): array {
84+
try {
85+
$access = $this->backend->getLDAPAccess($uid);
86+
$attrs = $access->userManager->getAttributes();
87+
$user = $access->userManager->get($uid);
88+
$avatarAttributes = $access->getConnection()->resolveRule('avatar');
89+
$baseDn = $this->helper->DNasBaseParameter($user->getDN());
90+
$result = $access->search('objectclass=*', $baseDn, $attrs, 1, 0);
91+
$attributes = [];
92+
foreach ($result[0] as $attribute => $valueSet) {
93+
foreach ($valueSet as $value) {
94+
if (in_array($attribute, $avatarAttributes)) {
95+
$value = '{ImageData}';
96+
}
97+
$attributes[$attribute] = $value;
98+
}
99+
}
100+
$access->batchApplyUserAttributes($result);
101+
return $attributes;
102+
} catch (\Exception $e) {
103+
throw new \Exception('Error while trying to lookup and update attributes from LDAP', previous: $e);
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)