Skip to content

Commit cd91e09

Browse files
Merge pull request #51305 from nextcloud/backport/50691/stable29
[stable29] Add LDAP test settings command
2 parents 8f59686 + b4f57d2 commit cd91e09

File tree

8 files changed

+299
-58
lines changed

8 files changed

+299
-58
lines changed

apps/user_ldap/appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ A user logs into Nextcloud with their LDAP or AD credentials, and is granted acc
5959
<command>OCA\User_LDAP\Command\ShowConfig</command>
6060
<command>OCA\User_LDAP\Command\ShowRemnants</command>
6161
<command>OCA\User_LDAP\Command\TestConfig</command>
62+
<command>OCA\User_LDAP\Command\TestUserSettings</command>
6263
<command>OCA\User_LDAP\Command\UpdateUUID</command>
6364
</commands>
6465

apps/user_ldap/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
'OCA\\User_LDAP\\Command\\ShowConfig' => $baseDir . '/../lib/Command/ShowConfig.php',
2424
'OCA\\User_LDAP\\Command\\ShowRemnants' => $baseDir . '/../lib/Command/ShowRemnants.php',
2525
'OCA\\User_LDAP\\Command\\TestConfig' => $baseDir . '/../lib/Command/TestConfig.php',
26+
'OCA\\User_LDAP\\Command\\TestUserSettings' => $baseDir . '/../lib/Command/TestUserSettings.php',
2627
'OCA\\User_LDAP\\Command\\UpdateUUID' => $baseDir . '/../lib/Command/UpdateUUID.php',
2728
'OCA\\User_LDAP\\Configuration' => $baseDir . '/../lib/Configuration.php',
2829
'OCA\\User_LDAP\\Connection' => $baseDir . '/../lib/Connection.php',

apps/user_ldap/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ComposerStaticInitUser_LDAP
3838
'OCA\\User_LDAP\\Command\\ShowConfig' => __DIR__ . '/..' . '/../lib/Command/ShowConfig.php',
3939
'OCA\\User_LDAP\\Command\\ShowRemnants' => __DIR__ . '/..' . '/../lib/Command/ShowRemnants.php',
4040
'OCA\\User_LDAP\\Command\\TestConfig' => __DIR__ . '/..' . '/../lib/Command/TestConfig.php',
41+
'OCA\\User_LDAP\\Command\\TestUserSettings' => __DIR__ . '/..' . '/../lib/Command/TestUserSettings.php',
4142
'OCA\\User_LDAP\\Command\\UpdateUUID' => __DIR__ . '/..' . '/../lib/Command/UpdateUUID.php',
4243
'OCA\\User_LDAP\\Configuration' => __DIR__ . '/..' . '/../lib/Configuration.php',
4344
'OCA\\User_LDAP\\Connection' => __DIR__ . '/..' . '/../lib/Connection.php',
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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+
namespace OCA\User_LDAP\Command;
9+
10+
use OCA\User_LDAP\Group_Proxy;
11+
use OCA\User_LDAP\Helper;
12+
use OCA\User_LDAP\Mapping\GroupMapping;
13+
use OCA\User_LDAP\Mapping\UserMapping;
14+
use OCA\User_LDAP\User\DeletedUsersIndex;
15+
use OCA\User_LDAP\User_Proxy;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
22+
class TestUserSettings extends Command {
23+
public function __construct(
24+
protected User_Proxy $backend,
25+
protected Group_Proxy $groupBackend,
26+
protected Helper $helper,
27+
protected DeletedUsersIndex $dui,
28+
protected UserMapping $mapping,
29+
protected GroupMapping $groupMapping,
30+
) {
31+
parent::__construct();
32+
}
33+
34+
protected function configure(): void {
35+
$this
36+
->setName('ldap:test-user-settings')
37+
->setDescription('Runs tests and show information about user related LDAP settings')
38+
->addArgument(
39+
'user',
40+
InputArgument::REQUIRED,
41+
'the user name as used in Nextcloud, or the LDAP DN'
42+
)
43+
->addOption(
44+
'group',
45+
'g',
46+
InputOption::VALUE_REQUIRED,
47+
'A group DN to check if the user is a member or not'
48+
)
49+
->addOption(
50+
'clearcache',
51+
null,
52+
InputOption::VALUE_NONE,
53+
'Clear the cache of the LDAP connection before the beginning of tests'
54+
)
55+
;
56+
}
57+
58+
protected function execute(InputInterface $input, OutputInterface $output): int {
59+
try {
60+
$uid = $input->getArgument('user');
61+
$access = $this->backend->getLDAPAccess($uid);
62+
$connection = $access->getConnection();
63+
if ($input->getOption('clearcache')) {
64+
$connection->clearCache();
65+
}
66+
$configPrefix = $connection->getConfigPrefix();
67+
$knownDn = '';
68+
if ($access->stringResemblesDN($uid)) {
69+
$knownDn = $uid;
70+
$username = $access->dn2username($uid);
71+
if ($username !== false) {
72+
$uid = $username;
73+
}
74+
}
75+
76+
$dn = $this->mapping->getDNByName($uid);
77+
if ($dn !== false) {
78+
$output->writeln("User <info>$dn</info> is mapped with account name <info>$uid</info>.");
79+
$uuid = $this->mapping->getUUIDByDN($dn);
80+
$output->writeln("Known UUID is <info>$uuid</info>.");
81+
if ($knownDn === '') {
82+
$knownDn = $dn;
83+
}
84+
} else {
85+
$output->writeln("User <info>$uid</info> is not mapped.");
86+
}
87+
88+
if ($knownDn === '') {
89+
return self::SUCCESS;
90+
}
91+
92+
if (!$access->isDNPartOfBase($knownDn, $access->getConnection()->ldapBaseUsers)) {
93+
$output->writeln(
94+
"User <info>$knownDn</info> is not in one of the configured user bases: <info>" .
95+
implode(',', $access->getConnection()->ldapBaseUsers) .
96+
'</info>.'
97+
);
98+
}
99+
100+
$output->writeln("Configuration prefix is <info>$configPrefix</info>");
101+
$output->writeln('');
102+
103+
$attributeNames = [
104+
'ldapExpertUsernameAttr',
105+
'ldapUuidUserAttribute',
106+
'ldapExpertUUIDUserAttr',
107+
'ldapQuotaAttribute',
108+
'ldapEmailAttribute',
109+
'ldapUserDisplayName',
110+
'ldapUserDisplayName2',
111+
'ldapExtStorageHomeAttribute',
112+
'ldapAttributePhone',
113+
'ldapAttributeWebsite',
114+
'ldapAttributeAddress',
115+
'ldapAttributeTwitter',
116+
'ldapAttributeFediverse',
117+
'ldapAttributeOrganisation',
118+
'ldapAttributeRole',
119+
'ldapAttributeHeadline',
120+
'ldapAttributeBiography',
121+
'ldapAttributeBirthDate',
122+
'ldapAttributePronouns',
123+
];
124+
$output->writeln('Attributes set in configuration:');
125+
foreach ($attributeNames as $attributeName) {
126+
if ($connection->$attributeName !== '') {
127+
$output->writeln("- $attributeName: <info>" . $connection->$attributeName . '</info>');
128+
}
129+
}
130+
131+
$filter = $connection->ldapUserFilter;
132+
$attrs = $access->userManager->getAttributes(true);
133+
$attrs[] = strtolower($connection->ldapExpertUsernameAttr);
134+
if ($connection->ldapUuidUserAttribute !== 'auto') {
135+
$attrs[] = strtolower($connection->ldapUuidUserAttribute);
136+
}
137+
$attrs[] = 'memberof';
138+
$attrs = array_values(array_unique($attrs));
139+
$attributes = $access->readAttributes($knownDn, $attrs, $filter);
140+
141+
if ($attributes === false) {
142+
$output->writeln(
143+
"LDAP read on <info>$knownDn</info> with filter <info>$filter</info> failed."
144+
);
145+
return self::FAILURE;
146+
}
147+
148+
$output->writeln("Attributes fetched from LDAP using filter <info>$filter</info>:");
149+
foreach ($attributes as $attribute => $value) {
150+
$output->writeln(
151+
"- $attribute: <info>" . json_encode($value) . '</info>'
152+
);
153+
}
154+
155+
$uuid = $access->getUUID($knownDn);
156+
if ($connection->ldapUuidUserAttribute === 'auto') {
157+
$output->writeln('<error>Failed to detect UUID attribute</error>');
158+
} else {
159+
$output->writeln('Detected UUID attribute: <info>' . $connection->ldapUuidUserAttribute . '</info>');
160+
}
161+
if ($uuid === false) {
162+
$output->writeln("<error>Failed to find UUID for $knownDn</error>");
163+
} else {
164+
$output->writeln("UUID for <info>$knownDn</info>: <info>$uuid</info>");
165+
}
166+
167+
$groupLdapInstance = $this->groupBackend->getBackend($configPrefix);
168+
169+
$output->writeln('');
170+
$output->writeln('Group information:');
171+
172+
$attributeNames = [
173+
'ldapDynamicGroupMemberURL',
174+
'ldapGroupFilter',
175+
'ldapGroupMemberAssocAttr',
176+
];
177+
$output->writeln('Configuration:');
178+
foreach ($attributeNames as $attributeName) {
179+
if ($connection->$attributeName !== '') {
180+
$output->writeln("- $attributeName: <info>" . $connection->$attributeName . '</info>');
181+
}
182+
}
183+
184+
$primaryGroup = $groupLdapInstance->getUserPrimaryGroup($knownDn);
185+
$output->writeln('Primary group: <info>' . ($primaryGroup !== false? $primaryGroup:'') . '</info>');
186+
187+
$groupByGid = $groupLdapInstance->getUserGroupByGid($knownDn);
188+
$output->writeln('Group from gidNumber: <info>' . ($groupByGid !== false? $groupByGid:'') . '</info>');
189+
190+
$groups = $groupLdapInstance->getUserGroups($uid);
191+
$output->writeln('All known groups: <info>' . json_encode($groups) . '</info>');
192+
193+
$memberOfUsed = ((int)$access->connection->hasMemberOfFilterSupport === 1
194+
&& (int)$access->connection->useMemberOfToDetectMembership === 1);
195+
196+
$output->writeln('MemberOf usage: <info>' . ($memberOfUsed ? 'on' : 'off') . '</info> (' . $access->connection->hasMemberOfFilterSupport . ',' . $access->connection->useMemberOfToDetectMembership . ')');
197+
198+
$gid = (string)$input->getOption('group');
199+
if ($gid === '') {
200+
return self::SUCCESS;
201+
}
202+
203+
$output->writeln('');
204+
$output->writeln("Group $gid:");
205+
$knownGroupDn = '';
206+
if ($access->stringResemblesDN($gid)) {
207+
$knownGroupDn = $gid;
208+
$groupname = $access->dn2groupname($gid);
209+
if ($groupname !== false) {
210+
$gid = $groupname;
211+
}
212+
}
213+
214+
$groupDn = $this->groupMapping->getDNByName($gid);
215+
if ($groupDn !== false) {
216+
$output->writeln("Group <info>$groupDn</info> is mapped with name <info>$gid</info>.");
217+
$groupUuid = $this->groupMapping->getUUIDByDN($groupDn);
218+
$output->writeln("Known UUID is <info>$groupUuid</info>.");
219+
if ($knownGroupDn === '') {
220+
$knownGroupDn = $groupDn;
221+
}
222+
} else {
223+
$output->writeln("Group <info>$gid</info> is not mapped.");
224+
}
225+
226+
$members = $groupLdapInstance->usersInGroup($gid);
227+
$output->writeln('Members: <info>' . json_encode($members) . '</info>');
228+
229+
return self::SUCCESS;
230+
231+
} catch (\Exception $e) {
232+
$output->writeln('<error>' . $e->getMessage() . '</error>');
233+
return self::FAILURE;
234+
}
235+
}
236+
}

apps/user_ldap/lib/Group_Proxy.php

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,11 @@
3939
use OCP\IConfig;
4040
use OCP\IUserManager;
4141

42+
/**
43+
* @template-extends Proxy<Group_LDAP>
44+
*/
4245
class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend, IBatchMethodsBackend, IIsAdminBackend {
43-
private $backends = [];
44-
private ?Group_LDAP $refBackend = null;
45-
private Helper $helper;
4646
private GroupPluginManager $groupPluginManager;
47-
private bool $isSetUp = false;
4847
private IConfig $config;
4948
private IUserManager $ncUserManager;
5049

@@ -56,28 +55,15 @@ public function __construct(
5655
IConfig $config,
5756
IUserManager $ncUserManager,
5857
) {
59-
parent::__construct($ldap, $accessFactory);
60-
$this->helper = $helper;
58+
parent::__construct($helper, $ldap, $accessFactory);
6159
$this->groupPluginManager = $groupPluginManager;
6260
$this->config = $config;
6361
$this->ncUserManager = $ncUserManager;
6462
}
6563

66-
protected function setup(): void {
67-
if ($this->isSetUp) {
68-
return;
69-
}
7064

71-
$serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true);
72-
foreach ($serverConfigPrefixes as $configPrefix) {
73-
$this->backends[$configPrefix] =
74-
new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager, $this->config, $this->ncUserManager);
75-
if (is_null($this->refBackend)) {
76-
$this->refBackend = &$this->backends[$configPrefix];
77-
}
78-
}
79-
80-
$this->isSetUp = true;
65+
protected function newInstance(string $configPrefix): Group_LDAP {
66+
return new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager, $this->config, $this->ncUserManager);
8167
}
8268

8369
/**
@@ -173,9 +159,7 @@ public function getUserGroups($uid) {
173159
$groups = [];
174160
foreach ($this->backends as $backend) {
175161
$backendGroups = $backend->getUserGroups($uid);
176-
if (is_array($backendGroups)) {
177-
$groups = array_merge($groups, $backendGroups);
178-
}
162+
$groups = array_merge($groups, $backendGroups);
179163
}
180164

181165
return array_values(array_unique($groups));

apps/user_ldap/lib/Proxy.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
use OCP\ICache;
3838
use OCP\Server;
3939

40+
/**
41+
* @template T
42+
*/
4043
abstract class Proxy {
4144
/** @var array<string,Access> */
4245
private static array $accesses = [];
@@ -45,7 +48,15 @@ abstract class Proxy {
4548
private ?ICache $cache = null;
4649
private AccessFactory $accessFactory;
4750

51+
/** @var T[] */
52+
protected array $backends = [];
53+
/** @var ?T */
54+
protected $refBackend = null;
55+
56+
protected bool $isSetUp = false;
57+
4858
public function __construct(
59+
private Helper $helper,
4960
ILDAPWrapper $ldap,
5061
AccessFactory $accessFactory
5162
) {
@@ -57,6 +68,36 @@ public function __construct(
5768
}
5869
}
5970

71+
protected function setup(): void {
72+
if ($this->isSetUp) {
73+
return;
74+
}
75+
76+
$serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true);
77+
foreach ($serverConfigPrefixes as $configPrefix) {
78+
$this->backends[$configPrefix] = $this->newInstance($configPrefix);
79+
80+
if (is_null($this->refBackend)) {
81+
$this->refBackend = $this->backends[$configPrefix];
82+
}
83+
}
84+
85+
$this->isSetUp = true;
86+
}
87+
88+
/**
89+
* @return T
90+
*/
91+
abstract protected function newInstance(string $configPrefix): object;
92+
93+
/**
94+
* @return T
95+
*/
96+
public function getBackend(string $configPrefix): object {
97+
$this->setup();
98+
return $this->backends[$configPrefix];
99+
}
100+
60101
private function addAccess(string $configPrefix): void {
61102
$userMap = Server::get(UserMapping::class);
62103
$groupMap = Server::get(GroupMapping::class);

0 commit comments

Comments
 (0)