Skip to content

Commit 627b8a9

Browse files
committed
If the passwd exop is not supported allow setting unicodePwd for AD
* Do not rely on exop_passwd but check rootDSE for support and fallback to mod_replace Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent 38a0aaf commit 627b8a9

File tree

5 files changed

+102
-31
lines changed

5 files changed

+102
-31
lines changed

lib/LDAPConnect.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ class LDAPConnect {
3333
private $ldapConfig;
3434
/** @var LoggerInterface */
3535
private $logger;
36+
/** @var bool|null */
37+
private $passwdSupport;
3638

3739
public function __construct(Helper $ldapBackendHelper, LoggerInterface $logger) {
40+
$this->passwdSupport = null;
3841
$this->logger = $logger;
3942
$ldapConfigPrefixes = $ldapBackendHelper->getServerConfigurationPrefixes(true);
4043
$prefix = array_shift($ldapConfigPrefixes);
@@ -143,4 +146,29 @@ public function hasPasswordPolicy(): bool {
143146
$ppDN = $this->ldapConfig->ldapDefaultPPolicyDN;
144147
return !empty($ppDN);
145148
}
149+
150+
/**
151+
* checks whether the LDAP server supports the passwd exop
152+
*
153+
* @param \LDAP\Connection $connection LDAP connection to check
154+
* @return boolean either the user can or cannot
155+
*/
156+
public function hasPasswdExopSupport($connection):bool {
157+
if (is_null($this->passwdSupport)) {
158+
$ret = ldap_read($connection, '', '(objectclass=*)', ['supportedExtension']);
159+
if ($ret === false) {
160+
$this->passwdSupport = false;
161+
return false;
162+
}
163+
$ret = ldap_first_entry($connection, $ret);
164+
if ($ret === false) {
165+
$this->passwdSupport = false;
166+
return false;
167+
}
168+
169+
$values = ldap_get_values($connection, $ret, 'supportedExtension');
170+
$this->passwdSupport = ($values !== false) && in_array(LDAP_EXOP_MODIFY_PASSWD, $values['supportedExtension']);
171+
}
172+
return $this->passwdSupport;
173+
}
146174
}

lib/LDAPUserManager.php

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use OCP\HintException;
3838
use OCP\IImage;
3939
use OCP\IL10N;
40+
use OCP\ILogger;
4041
use OCP\IUser;
4142
use OCP\IUserManager;
4243
use OCP\IUserSession;
@@ -84,7 +85,7 @@ public function __construct(IUserManager $userManager, IUserSession $userSession
8485
* @return int bitwise-or'ed actions
8586
*/
8687
public function respondToActions() {
87-
$setPassword = function_exists('ldap_exop_passwd') && !$this->ldapConnect->hasPasswordPolicy()
88+
$setPassword = $this->canSetPassword() && !$this->ldapConnect->hasPasswordPolicy()
8889
? Backend::SET_PASSWORD
8990
: 0;
9091

@@ -244,27 +245,8 @@ public function createUser($username, $password) {
244245
throw new \Exception('Cannot create user: ' . ldap_error($connection), ldap_errno($connection));
245246
}
246247

247-
// Set password through ldap password exop, if supported
248248
if ($this->respondToActions() & Backend::SET_PASSWORD) {
249-
try {
250-
$ret = ldap_exop_passwd($connection, $newUserDN, '', $password);
251-
if ($ret === false) {
252-
$message = 'ldap_exop_passwd failed, falling back to ldap_mod_replace to to set password for new user';
253-
$this->logger->debug($message, ['app' => Application::APP_ID]);
254-
255-
// Fallback to `userPassword` in case the server does not support exop_passwd
256-
$ret = ldap_mod_replace($connection, $newUserDN, ['userPassword' => $password]);
257-
if ($ret === false) {
258-
$message = 'Failed to set password for new user {dn}';
259-
$this->logger->error($message, [
260-
'app' => Application::APP_ID,
261-
'dn' => $newUserDN,
262-
]);
263-
}
264-
}
265-
} catch (\Exception $e) {
266-
$this->logger->error($e->getMessage(), ['exception' => $e, 'app' => Application::APP_ID]);
267-
}
249+
$this->setPassword($username, $password, $connection);
268250
}
269251
return $ret ? $newUserDN : false;
270252
}
@@ -350,30 +332,72 @@ public function deleteUser($uid): bool {
350332
return $res;
351333
}
352334

335+
/**
336+
* checks whether the user is allowed to change their password in Nextcloud
337+
*
338+
* @return boolean either the user can or cannot
339+
*/
340+
public function canSetPassword(): bool {
341+
return $this->configuration->hasPasswordPermission();
342+
}
343+
353344
/**
354345
* Set password
355346
*
356347
* @param string $uid The username
357348
* @param string $password The new password
349+
* @param ?LDAP\Connection $connection LDAP connection or null to create one
358350
* @return bool
359351
*
360352
* Change the password of a user
361353
*/
362-
public function setPassword($uid, $password) {
363-
if (!function_exists('ldap_exop_passwd')) {
364-
// since PHP 7.2 – respondToActions checked this already, this
365-
// method should not be called. Double check due to public scope.
366-
// This method can be removed when Nextcloud 16 compat is dropped.
367-
return false;
354+
public function setPassword($uid, $password, $connection = null) {
355+
if (is_null($connection)) {
356+
$connection = $this->ldapProvider->getLDAPConnection($uid);
368357
}
358+
369359
try {
370-
$cr = $this->ldapProvider->getLDAPConnection($uid);
371360
$userDN = $this->getUserDN($uid);
372-
return ldap_exop_passwd($cr, $userDN, '', $password) !== false;
361+
$ret = false;
362+
363+
// try ldap_exop_passwd first
364+
if ($this->ldapConnect->hasPasswdExopSupport($connection)) {
365+
$ret = ldap_exop_passwd($connection, $userDN, '', $password);
366+
if ($ret === false) {
367+
$message = 'Failed to set password for user {dn} using ldap_exop_passwd';
368+
$this->logger->error($message, [
369+
'ldap_error' => ldap_error($connection),
370+
'app' => Application::APP_ID,
371+
'dn' => $userDN,
372+
]);
373+
}
374+
} else {
375+
// Fallback to `userPassword` in case the server does not support exop_passwd
376+
$entry = [];
377+
if ($this->configuration->useUnicodePassword()) {
378+
$entry['unicodePwd'] = iconv('UTF-8', 'UTF-16LE', '"' . $password . '"');
379+
} else {
380+
$entry['userPassword'] = $password;
381+
}
382+
$ret = ldap_mod_replace($connection, $userDN, $entry);
383+
if ($ret === false) {
384+
$message = 'Failed to set password for user {dn} using ldap_mod_replace';
385+
$this->logger->error($message, [
386+
'ldap_error' => ldap_error($connection),
387+
'app' => Application::APP_ID,
388+
'dn' => $userDN,
389+
]);
390+
}
391+
}
392+
return $ret;
373393
} catch (\Exception $e) {
374-
$this->logger->error($e->getMessage(), ['exception' => $e, 'app' => Application::APP_ID]);
394+
$this->logger->error('Exception occured while setting the password of user {dn}', [
395+
'app' => Application::APP_ID,
396+
'exception' => $e,
397+
'dn' => $uid
398+
]);
399+
return false;
375400
}
376-
return false;
377401
}
378402

379403
/**

lib/Service/Configuration.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public function hasAvatarPermission(): bool {
4848
return $this->config->getAppValue('ldap_write_support', 'hasAvatarPermission', '1') === '1';
4949
}
5050

51+
public function hasPasswordPermission(): bool {
52+
return $this->config->getAppValue('ldap_write_support', 'hasPasswordPermission', '1') === '1';
53+
}
54+
55+
public function useUnicodePassword(): bool {
56+
return $this->config->getAppValue('ldap_write_support', 'useUnicodePassword', '0') === '1';
57+
}
58+
5159
public function getUserTemplate() {
5260
return $this->config->getAppValue(
5361
Application::APP_ID,

lib/Settings/Admin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ public function getForm() {
6161
'createRequireActorFromLdap' => $this->config->isLdapActorRequired(),
6262
'createPreventFallback' => $this->config->isPreventFallback(),
6363
'hasAvatarPermission' => $this->config->hasAvatarPermission(),
64+
'hasPasswordPermission' => $this->config->hasPasswordPermission(),
6465
'newUserRequireEmail' => $this->config->isRequireEmail(),
6566
'newUserGenerateUserID' => $this->config->isGenerateUserId(),
67+
'useUnicodePassword' => $this->config->useUnicodePassword(),
6668
]
6769
);
6870
return new TemplateResponse(Application::APP_ID, 'settings-admin');

src/components/AdminSettings.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@
4444
@change.stop.prevent="toggleSwitch('hasAvatarPermission', !switches.hasAvatarPermission)">
4545
{{ t('ldap_write_support', 'Allow users to set their avatar') }}
4646
</NcActionCheckbox>
47+
<NcActionCheckbox :checked="switches.hasPasswordPermission"
48+
@change.stop.prevent="toggleSwitch('hasPasswordPermission', !switches.hasPasswordPermission)">
49+
{{ t('ldap_write_support', 'Allow users to set their password') }}
50+
</NcActionCheckbox>
51+
<NcActionCheckbox :checked="switches.useUnicodePassword"
52+
:title="t('ldap_write_support', 'If the server does not support the modify password extended operation use the `unicodePwd` instead of the `userPassword` attribute for setting the password')"
53+
@change.stop.prevent="toggleSwitch('useUnicodePassword', !switches.useUnicodePassword)">
54+
{{ t('ldap_write_support', 'Use the `unicodePwd` attribute for setting the user password') }}
55+
</NcActionCheckbox>
4756
</ul>
4857
<h3>{{ t('ldap_write_support', 'User template') }}</h3>
4958
<p>{{ t('ldap_write_support', 'LDIF template for creating users. Following placeholders may be used') }}</p>

0 commit comments

Comments
 (0)