Skip to content

Commit 911c56f

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 911c56f

6 files changed

Lines changed: 534 additions & 76 deletions

File tree

apps/user_ldap/lib/Command/CheckUser.php

Lines changed: 16 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use OCA\User_LDAP\Helper;
1212
use OCA\User_LDAP\Mapping\UserMapping;
13+
use OCA\User_LDAP\Service\CheckUserService;
1314
use OCA\User_LDAP\User\DeletedUsersIndex;
1415
use OCA\User_LDAP\User_Proxy;
1516
use OCP\IUserManager;
@@ -21,11 +22,8 @@
2122

2223
class CheckUser extends Command {
2324
public function __construct(
24-
protected User_Proxy $backend,
25-
protected Helper $helper,
26-
protected DeletedUsersIndex $dui,
27-
protected UserMapping $mapping,
28-
protected IUserManager $userManager,
25+
private readonly IUserManager $userManager,
26+
private readonly CheckUserService $checkUserService
2927
) {
3028
parent::__construct();
3129
}
@@ -77,7 +75,11 @@ protected function configure(): void {
7775
#[\Override]
7876
protected function execute(InputInterface $input, OutputInterface $output): int {
7977
try {
80-
$this->assertAllowed($input->getOption('force'));
78+
if (!$this->checkUserService->assertAllowed($input->getOption('force'))) {
79+
throw new \Exception('Cannot check user existence, because '
80+
. 'disabled LDAP configurations are present.');
81+
}
82+
8183
$uid = $input->getArgument('ocName');
8284

8385
if ($uid !== null) {
@@ -109,77 +111,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
109111
}
110112

111113
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) {
114+
$result = $this->checkUserService->checkUser($uid, $input->getOption('update'));
115+
if ($result['exists']) {
121116
$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);
117+
} else if ($result['wasMapped']) {
130118
$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.');
119+
$output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' . $uid . '"');
156120
}
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-
}
121+
if (isset($result['attributes'])) {
122+
foreach ($result['attributes'] as $attribute => $value) {
123+
$output->writeln(' ' . $attribute . ': ' . $value);
179124
}
180-
$access->batchApplyUserAttributes($result);
181-
} catch (\Exception $e) {
182-
$output->writeln('<error>Error while trying to lookup and update attributes from LDAP</error>');
183125
}
126+
return self::SUCCESS;
184127
}
185128
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\ConnectionFactory;
14+
use OCA\User_LDAP\Exceptions\ConfigurationIssueException;
15+
use OCA\User_LDAP\Helper;
16+
use OCA\User_LDAP\ILDAPWrapper;
17+
use OCA\User_LDAP\LDAP;
18+
use OCA\User_LDAP\Service\CheckUserService;
19+
use OCA\User_LDAP\Settings\Admin;
20+
use OCP\AppFramework\Http;
21+
use OCP\AppFramework\Http\Attribute\ApiRoute;
22+
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
23+
use OCP\AppFramework\Http\DataResponse;
24+
use OCP\AppFramework\OCS\OCSBadRequestException;
25+
use OCP\AppFramework\OCS\OCSException;
26+
use OCP\AppFramework\OCS\OCSForbiddenException;
27+
use OCP\AppFramework\OCS\OCSNotFoundException;
28+
use OCP\AppFramework\OCSController;
29+
use OCP\IL10N;
30+
use OCP\IRequest;
31+
use OCP\Server;
32+
use Psr\Log\LoggerInterface;
33+
34+
class APIController extends OCSController {
35+
public function __construct(
36+
string $appName,
37+
IRequest $request,
38+
private readonly CheckUserService $checkUserService,
39+
) {
40+
parent::__construct($appName, $request);
41+
}
42+
43+
/**
44+
* Check a user and update its attribute in the LDAP server
45+
*
46+
* @param string $userID ID of the user
47+
* @return DataResponse<Http::STATUS_OK, array{exists:bool, wasMapped: bool, attributes?: array<string, string>}, array{}>
48+
* @throws OCSException An unexpected error happened
49+
* @throws OCSForbiddenException Cannot check user existence
50+
*
51+
* 200: The user configuration as found in the LDAP server
52+
*/
53+
#[ApiRoute(verb: 'POST', url: '/api/v1/checkUser/{userID}')]
54+
#[AuthorizedAdminSetting(settings: Admin::class)]
55+
public function checkUser(string $userID): DataResponse {
56+
if ($this->checkUserService->assertAllowed(false)) {
57+
throw new OCSForbiddenException('Cannot check user existence, because disabled LDAP configurations are present.');
58+
}
59+
60+
$result = $this->checkUserService->checkUser($userID, true);
61+
return new DataResponse($result);
62+
}
63+
}

apps/user_ldap/lib/Controller/ConfigAPIController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
use OCA\User_LDAP\Helper;
1414
use OCA\User_LDAP\ILDAPWrapper;
1515
use OCA\User_LDAP\LDAP;
16+
use OCA\User_LDAP\Service\CheckUserService;
1617
use OCA\User_LDAP\Settings\Admin;
1718
use OCP\AppFramework\Http;
1819
use OCP\AppFramework\Http\Attribute\ApiRoute;
1920
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
2021
use OCP\AppFramework\Http\DataResponse;
2122
use OCP\AppFramework\OCS\OCSBadRequestException;
2223
use OCP\AppFramework\OCS\OCSException;
24+
use OCP\AppFramework\OCS\OCSForbiddenException;
2325
use OCP\AppFramework\OCS\OCSNotFoundException;
2426
use OCP\AppFramework\OCSController;
2527
use OCP\IL10N;
@@ -314,7 +316,7 @@ public function testConfiguration(string $configID) {
314316
*/
315317
#[AuthorizedAdminSetting(settings: Admin::class)]
316318
#[ApiRoute(verb: 'POST', url: '/api/v1/config/{configID}/copy')]
317-
public function copyConfiguration(string $configID) {
319+
public function copyConfiguration(string $configID): DataResponse {
318320
try {
319321
$this->ensureConfigIDExists($configID);
320322
$configPrefix = $this->ldapHelper->getNextServerConfigurationPrefix();
@@ -337,8 +339,7 @@ public function copyConfiguration(string $configID) {
337339
* @param string $configID
338340
* @throws OCSNotFoundException
339341
*/
340-
#[AuthorizedAdminSetting(settings: Admin::class)]
341-
private function ensureConfigIDExists($configID): void {
342+
private function ensureConfigIDExists(string $configID): void {
342343
$prefixes = $this->ldapHelper->getServerConfigurationPrefixes();
343344
if (!in_array($configID, $prefixes, true)) {
344345
throw new OCSNotFoundException('Config ID not found');
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
use Symfony\Component\Console\Command\Command;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
23+
class CheckUserService {
24+
public function __construct(
25+
protected readonly User_Proxy $backend,
26+
protected readonly Helper $helper,
27+
protected readonly DeletedUsersIndex $dui,
28+
protected readonly UserMapping $mapping,
29+
protected readonly IUserManager $userManager,
30+
) {
31+
}
32+
33+
/**
34+
* @param string $uid
35+
* @param bool $update
36+
* @return array{exists: true, wasMapped: true, attributes?: array<string, string>}
37+
* @throws UserNotFoundException
38+
* @throws \OCP\PreConditionNotMetException
39+
*/
40+
public function checkUser(string $uid, bool $update): int {
41+
if ($this->backend->getLDAPAccess($uid)->stringResemblesDN($uid)) {
42+
$username = $this->backend->dn2UserName($uid);
43+
if ($username !== false) {
44+
$uid = $username;
45+
}
46+
}
47+
$wasMapped = $this->userWasMapped($uid);
48+
$exists = $this->backend->userExistsOnLDAP($uid, true);
49+
if ($exists === true) {
50+
if ($update) {
51+
$attributes = $this->updateUser($uid);
52+
return ['exists' => true, 'wasMapped' => $wasMapped, 'attributes' => $attributes];
53+
}
54+
return ['exists' => true, 'wasMapped' => $wasMapped];
55+
}
56+
57+
if (!$wasMapped) {
58+
throw new UserNotFoundException('The given user is not a recognized LDAP user.');
59+
}
60+
61+
$this->dui->markUser($uid);
62+
return ['exists' => true, 'wasMapped' => $wasMapped];
63+
}
64+
65+
/**
66+
* checks whether a user is actually mapped
67+
* @param string $ocName the username as used in Nextcloud
68+
*/
69+
protected function userWasMapped(string $ocName): bool {
70+
$dn = $this->mapping->getDNByName($ocName);
71+
return $dn !== false;
72+
}
73+
74+
/**
75+
* Checks whether the setup allows reliable checking of LDAP user existence
76+
*/
77+
public function assertAllowed(bool $force): bool {
78+
// we don't check ldapUserCleanupInterval from config.php because this
79+
// action is triggered manually, while the setting only controls the
80+
// background job.
81+
return !$this->helper->haveDisabledConfigurations() || $force;
82+
}
83+
84+
/**
85+
* @param string $uid
86+
* @return array<string, string> The attributes
87+
*/
88+
private function updateUser(string $uid): array {
89+
try {
90+
$access = $this->backend->getLDAPAccess($uid);
91+
$attrs = $access->userManager->getAttributes();
92+
$user = $access->userManager->get($uid);
93+
$avatarAttributes = $access->getConnection()->resolveRule('avatar');
94+
$baseDn = $this->helper->DNasBaseParameter($user->getDN());
95+
$result = $access->search('objectclass=*', $baseDn, $attrs, 1, 0);
96+
$attributes = [];
97+
foreach ($result[0] as $attribute => $valueSet) {
98+
foreach ($valueSet as $value) {
99+
if (in_array($attribute, $avatarAttributes)) {
100+
$value = '{ImageData}';
101+
}
102+
$attributes[$attribute] = $value;
103+
}
104+
}
105+
$access->batchApplyUserAttributes($result);
106+
return $attributes;
107+
} catch (\Exception $e) {
108+
throw new \Exception('Error while trying to lookup and update attributes from LDAP', previous: $e);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)