Skip to content

Commit d7c442d

Browse files
authored
Merge pull request #33443 from nextcloud/backport/33407/stable23
[stable23] Handle one time and large passwords
2 parents 4d84690 + bc29ff5 commit d7c442d

File tree

4 files changed

+119
-8
lines changed

4 files changed

+119
-8
lines changed

apps/settings/lib/Controller/ChangePasswordController.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public function changePersonalPassword(string $oldpassword = '', string $newpass
107107
}
108108

109109
try {
110-
if ($newpassword === null || $user->setPassword($newpassword) === false) {
110+
if ($newpassword === null || strlen($newpassword) > 469 || $user->setPassword($newpassword) === false) {
111111
return new JSONResponse([
112112
'status' => 'error'
113113
]);
@@ -155,6 +155,16 @@ public function changeUserPassword(string $username = null, string $password = n
155155
]);
156156
}
157157

158+
if (strlen($password) > 469) {
159+
return new JSONResponse([
160+
'status' => 'error',
161+
'data' => [
162+
'message' => $this->l->t('Unable to change password. Password too long.'),
163+
],
164+
]);
165+
}
166+
167+
158168
$currentUser = $this->userSession->getUser();
159169
$targetUser = $this->userManager->get($username);
160170
if ($currentUser === null || $targetUser === null ||

config/config.sample.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,21 @@
318318
*/
319319
'auth.webauthn.enabled' => true,
320320

321+
/**
322+
* Whether encrypted password should be stored in the database
323+
*
324+
* The passwords are only decrypted using the login token stored uniquely in the
325+
* clients and allow to connect to external storages, autoconfigure mail account in
326+
* the mail app and periodically check if the password it still valid.
327+
*
328+
* This might be desirable to disable this functionality when using one time
329+
* passwords or when having a password policy enforcing long passwords (> 300
330+
* characters).
331+
*
332+
* By default the passwords are stored encrypted in the database.
333+
*/
334+
'auth.storeCryptedPassword' => true,
335+
321336
/**
322337
* By default the login form is always available. There are cases (SSO) where an
323338
* admin wants to avoid users entering their credentials to the system if the SSO

lib/private/Authentication/Token/PublicKeyTokenProvider.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ private function newToken(string $token,
370370

371371
$config = array_merge([
372372
'digest_alg' => 'sha512',
373-
'private_key_bits' => 2048,
373+
'private_key_bits' => $password !== null && strlen($password) > 250 ? 4096 : 2048,
374374
], $this->config->getSystemValue('openssl', []));
375375

376376
// Generate new key
@@ -392,7 +392,10 @@ private function newToken(string $token,
392392
$dbToken->setPublicKey($publicKey);
393393
$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
394394

395-
if (!is_null($password)) {
395+
if (!is_null($password) && $this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
396+
if (strlen($password) > 469) {
397+
throw new \RuntimeException('Trying to save a password with more than 469 characters is not supported. If you want to use big passwords, disable the auth.storeCryptedPassword option in config.php');
398+
}
396399
$dbToken->setPassword($this->encryptPassword($password, $publicKey));
397400
}
398401

@@ -422,7 +425,7 @@ public function updatePasswords(string $uid, string $password) {
422425
$this->cache->clear();
423426

424427
// prevent setting an empty pw as result of pw-less-login
425-
if ($password === '') {
428+
if ($password === '' || !$this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
426429
return;
427430
}
428431

tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
use OC\Authentication\Exceptions\ExpiredTokenException;
2727
use OC\Authentication\Exceptions\InvalidTokenException;
28+
use OC\Authentication\Exceptions\PasswordlessTokenException;
2829
use OC\Authentication\Token\DefaultToken;
2930
use OC\Authentication\Token\IToken;
3031
use OC\Authentication\Token\PublicKeyToken;
@@ -85,6 +86,10 @@ public function testGenerateToken() {
8586
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
8687
$type = IToken::PERMANENT_TOKEN;
8788

89+
$this->config->method('getSystemValueBool')
90+
->willReturnMap([
91+
['auth.storeCryptedPassword', true, true],
92+
]);
8893
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
8994

9095
$this->assertInstanceOf(PublicKeyToken::class, $actual);
@@ -95,6 +100,48 @@ public function testGenerateToken() {
95100
$this->assertSame($password, $this->tokenProvider->getPassword($actual, $token));
96101
}
97102

103+
public function testGenerateTokenNoPassword(): void {
104+
$token = 'token';
105+
$uid = 'user';
106+
$user = 'User';
107+
$password = 'passme';
108+
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
109+
$type = IToken::PERMANENT_TOKEN;
110+
$this->config->method('getSystemValueBool')
111+
->willReturnMap([
112+
['auth.storeCryptedPassword', true, false],
113+
]);
114+
$this->expectException(PasswordlessTokenException::class);
115+
116+
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
117+
118+
$this->assertInstanceOf(PublicKeyToken::class, $actual);
119+
$this->assertSame($uid, $actual->getUID());
120+
$this->assertSame($user, $actual->getLoginName());
121+
$this->assertSame($name, $actual->getName());
122+
$this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember());
123+
$this->tokenProvider->getPassword($actual, $token);
124+
}
125+
126+
public function testGenerateTokenLongPassword() {
127+
$token = 'token';
128+
$uid = 'user';
129+
$user = 'User';
130+
$password = '';
131+
for ($i = 0; $i < 500; $i++) {
132+
$password .= 'e';
133+
}
134+
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
135+
$type = IToken::PERMANENT_TOKEN;
136+
$this->config->method('getSystemValueBool')
137+
->willReturnMap([
138+
['auth.storeCryptedPassword', true, true],
139+
]);
140+
$this->expectException(\RuntimeException::class);
141+
142+
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
143+
}
144+
98145
public function testGenerateTokenInvalidName() {
99146
$token = 'token';
100147
$uid = 'user';
@@ -105,6 +152,10 @@ public function testGenerateTokenInvalidName() {
105152
. 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'
106153
. 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
107154
$type = IToken::PERMANENT_TOKEN;
155+
$this->config->method('getSystemValueBool')
156+
->willReturnMap([
157+
['auth.storeCryptedPassword', true, true],
158+
]);
108159

109160
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
110161

@@ -122,6 +173,10 @@ public function testUpdateToken() {
122173
->method('updateActivity')
123174
->with($tk, $this->time);
124175
$tk->setLastActivity($this->time - 200);
176+
$this->config->method('getSystemValueBool')
177+
->willReturnMap([
178+
['auth.storeCryptedPassword', true, true],
179+
]);
125180

126181
$this->tokenProvider->updateTokenActivity($tk);
127182

@@ -159,6 +214,10 @@ public function testGetPassword() {
159214
$password = 'passme';
160215
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
161216
$type = IToken::PERMANENT_TOKEN;
217+
$this->config->method('getSystemValueBool')
218+
->willReturnMap([
219+
['auth.storeCryptedPassword', true, true],
220+
]);
162221

163222
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
164223

@@ -187,6 +246,10 @@ public function testGetPasswordInvalidToken() {
187246
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
188247
$type = IToken::PERMANENT_TOKEN;
189248

249+
$this->config->method('getSystemValueBool')
250+
->willReturnMap([
251+
['auth.storeCryptedPassword', true, true],
252+
]);
190253
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
191254

192255
$this->tokenProvider->getPassword($actual, 'wrongtoken');
@@ -199,6 +262,10 @@ public function testSetPassword() {
199262
$password = 'passme';
200263
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
201264
$type = IToken::PERMANENT_TOKEN;
265+
$this->config->method('getSystemValueBool')
266+
->willReturnMap([
267+
['auth.storeCryptedPassword', true, true],
268+
]);
202269

203270
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
204271

@@ -303,14 +370,18 @@ public function testRenewSessionTokenWithoutPassword() {
303370
$this->tokenProvider->renewSessionToken('oldId', 'newId');
304371
}
305372

306-
public function testRenewSessionTokenWithPassword() {
373+
public function testRenewSessionTokenWithPassword(): void {
307374
$token = 'oldId';
308375
$uid = 'user';
309376
$user = 'User';
310377
$password = 'password';
311378
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
312379
$type = IToken::PERMANENT_TOKEN;
313380

381+
$this->config->method('getSystemValueBool')
382+
->willReturnMap([
383+
['auth.storeCryptedPassword', true, true],
384+
]);
314385
$oldToken = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
315386

316387
$this->mapper
@@ -321,7 +392,7 @@ public function testRenewSessionTokenWithPassword() {
321392
$this->mapper
322393
->expects($this->once())
323394
->method('insert')
324-
->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name) {
395+
->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name): bool {
325396
return $token->getUID() === $uid &&
326397
$token->getLoginName() === $user &&
327398
$token->getName() === $name &&
@@ -333,14 +404,14 @@ public function testRenewSessionTokenWithPassword() {
333404
$this->mapper
334405
->expects($this->once())
335406
->method('delete')
336-
->with($this->callback(function ($token) use ($oldToken) {
407+
->with($this->callback(function ($token) use ($oldToken): bool {
337408
return $token === $oldToken;
338409
}));
339410

340411
$this->tokenProvider->renewSessionToken('oldId', 'newId');
341412
}
342413

343-
public function testGetToken() {
414+
public function testGetToken(): void {
344415
$token = new PublicKeyToken();
345416

346417
$this->config->method('getSystemValue')
@@ -443,6 +514,10 @@ public function testRotate() {
443514
$name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
444515
$type = IToken::PERMANENT_TOKEN;
445516

517+
$this->config->method('getSystemValueBool')
518+
->willReturnMap([
519+
['auth.storeCryptedPassword', true, true],
520+
]);
446521
$actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
447522

448523
$new = $this->tokenProvider->rotate($actual, 'oldtoken', 'newtoken');
@@ -487,6 +562,10 @@ public function testConvertToken() {
487562
->method('update')
488563
->willReturnArgument(0);
489564

565+
$this->config->method('getSystemValueBool')
566+
->willReturnMap([
567+
['auth.storeCryptedPassword', true, true],
568+
]);
490569
$newToken = $this->tokenProvider->convertToken($defaultToken, 'newToken', 'newPassword');
491570

492571
$this->assertSame(42, $newToken->getId());
@@ -540,6 +619,10 @@ public function testUpdatePasswords() {
540619
'random2',
541620
IToken::PERMANENT_TOKEN,
542621
IToken::REMEMBER);
622+
$this->config->method('getSystemValueBool')
623+
->willReturnMap([
624+
['auth.storeCryptedPassword', true, true],
625+
]);
543626

544627
$this->mapper->method('hasExpiredTokens')
545628
->with($uid)

0 commit comments

Comments
 (0)