Skip to content

Commit 3319975

Browse files
committed
feat(login): Add rememberme checkbox
Only present if allowed by configuration. Signed-off-by: Côme Chilliet <[email protected]>
1 parent 58e856e commit 3319975

File tree

7 files changed

+85
-22
lines changed

7 files changed

+85
-22
lines changed

core/Controller/LoginController.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ public function showLoginForm(?string $user = null, ?string $redirect_url = null
143143
$this->config->getSystemValue('login_form_autocomplete', true) === true
144144
);
145145

146+
$this->initialState->provideInitialState(
147+
'loginCanRememberme',
148+
$this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) > 0
149+
);
150+
146151
if (!empty($redirect_url)) {
147152
[$url, ] = explode('?', $redirect_url);
148153
if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
@@ -287,6 +292,7 @@ public function tryLogin(
287292
ITrustedDomainHelper $trustedDomainHelper,
288293
string $user = '',
289294
string $password = '',
295+
bool $rememberme = false,
290296
?string $redirect_url = null,
291297
string $timezone = '',
292298
string $timezone_offset = '',
@@ -339,9 +345,10 @@ public function tryLogin(
339345
$this->request,
340346
$user,
341347
$password,
348+
$rememberme,
342349
$redirect_url,
343350
$timezone,
344-
$timezone_offset
351+
$timezone_offset,
345352
);
346353
$result = $loginChain->process($data);
347354
if (!$result->isSuccess()) {

core/src/components/login/LoginForm.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@
8484
data-login-form-input-password
8585
required />
8686

87+
<NcCheckboxRadioSwitch
88+
v-if="remembermeAllowed"
89+
id="rememberme"
90+
ref="rememberme"
91+
name="rememberme"
92+
value="1"
93+
:checked.sync="rememberme"
94+
data-login-form-input-rememberme>
95+
{{ t('core', 'Remember me') }}
96+
</NcCheckboxRadioSwitch>
97+
8798
<LoginButton data-login-form-submit :loading="loading" />
8899

89100
<input
@@ -117,6 +128,7 @@ import { loadState } from '@nextcloud/initial-state'
117128
import { translate as t } from '@nextcloud/l10n'
118129
import { generateUrl, imagePath } from '@nextcloud/router'
119130
import debounce from 'debounce'
131+
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
120132
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
121133
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
122134
import NcTextField from '@nextcloud/vue/components/NcTextField'
@@ -128,6 +140,7 @@ export default {
128140
129141
components: {
130142
LoginButton,
143+
NcCheckboxRadioSwitch,
131144
NcPasswordField,
132145
NcTextField,
133146
NcNoteCard,
@@ -166,6 +179,11 @@ export default {
166179
default: true,
167180
},
168181
182+
remembermeAllowed: {
183+
type: Boolean,
184+
default: true,
185+
},
186+
169187
directLogin: {
170188
type: Boolean,
171189
default: false,
@@ -200,6 +218,7 @@ export default {
200218
loading: false,
201219
user: props.username,
202220
password: '',
221+
rememberme: [],
203222
visible: false,
204223
}
205224
},

core/src/views/Login.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
:errors="errors"
1717
:throttle-delay="throttleDelay"
1818
:auto-complete-allowed="autoCompleteAllowed"
19+
:rememberme-allowed="remembermeAllowed"
1920
:email-states="emailStates"
2021
@submit="loading = true" />
2122
<NcButton
@@ -148,6 +149,7 @@ export default {
148149
canResetPassword: loadState('core', 'loginCanResetPassword', false),
149150
resetPasswordLink: loadState('core', 'loginResetPasswordLink', ''),
150151
autoCompleteAllowed: loadState('core', 'loginAutocomplete', true),
152+
remembermeAllowed: loadState('core', 'loginCanRememberme', true),
151153
resetPasswordTarget: loadState('core', 'resetPasswordTarget', ''),
152154
resetPasswordUser: loadState('core', 'resetPasswordUser', ''),
153155
directLogin: query.direct === '1',

lib/private/Authentication/Login/CreateSessionTokenCommand.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ public function __construct(IConfig $config,
2626
}
2727

2828
public function process(LoginData $loginData): LoginResult {
29-
$tokenType = IToken::REMEMBER;
3029
if ($this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) === 0) {
3130
$loginData->setRememberLogin(false);
31+
}
32+
if ($loginData->isRememberLogin()) {
33+
$tokenType = IToken::REMEMBER;
34+
} else {
3235
$tokenType = IToken::DO_NOT_REMEMBER;
3336
}
3437

lib/private/Authentication/Login/LoginData.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ class LoginData {
1616
/** @var IUser|false|null */
1717
private $user = null;
1818

19-
private bool $rememberLogin = true;
20-
2119
public function __construct(
2220
private IRequest $request,
2321
private string $username,
2422
private ?string $password,
23+
private bool $rememberLogin = true,
2524
private ?string $redirectUrl = null,
2625
private string $timeZone = '',
2726
private string $timeZoneOffset = '',

tests/Core/Controller/LoginControllerTest.php

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use OCP\Notification\IManager;
3232
use OCP\Security\Bruteforce\IThrottler;
3333
use OCP\Security\ITrustedDomainHelper;
34+
use PHPUnit\Framework\Attributes\DataProvider;
3435
use PHPUnit\Framework\MockObject\MockObject;
3536
use Test\TestCase;
3637

@@ -277,7 +278,7 @@ public function testShowLoginFormWithErrorsInSession(): void {
277278
'',
278279
]
279280
];
280-
$this->initialState->expects($this->exactly(13))
281+
$this->initialState->expects($this->exactly(14))
281282
->method('provideInitialState')
282283
->willReturnCallback(function () use (&$calls): void {
283284
$expected = array_shift($calls);
@@ -309,12 +310,16 @@ public function testShowLoginFormForFlowAuth(): void {
309310
'loginAutocomplete',
310311
false
311312
],
313+
[
314+
'loginCanRememberme',
315+
false
316+
],
312317
[
313318
'loginRedirectUrl',
314319
'login/flow'
315320
],
316321
];
317-
$this->initialState->expects($this->exactly(14))
322+
$this->initialState->expects($this->exactly(15))
318323
->method('provideInitialState')
319324
->willReturnCallback(function () use (&$calls): void {
320325
$expected = array_shift($calls);
@@ -351,7 +356,7 @@ public static function passwordResetDataProvider(): array {
351356
];
352357
}
353358

354-
#[\PHPUnit\Framework\Attributes\DataProvider('passwordResetDataProvider')]
359+
#[DataProvider('passwordResetDataProvider')]
355360
public function testShowLoginFormWithPasswordResetOption($canChangePassword,
356361
$expectedResult): void {
357362
$this->userSession
@@ -386,13 +391,13 @@ public function testShowLoginFormWithPasswordResetOption($canChangePassword,
386391
'loginUsername',
387392
'LdapUser'
388393
],
389-
[], [], [],
394+
[], [], [], [],
390395
[
391396
'loginCanResetPassword',
392397
$expectedResult
393398
],
394399
];
395-
$this->initialState->expects($this->exactly(13))
400+
$this->initialState->expects($this->exactly(14))
396401
->method('provideInitialState')
397402
->willReturnCallback(function () use (&$calls): void {
398403
$expected = array_shift($calls);
@@ -445,6 +450,10 @@ public function testShowLoginFormForUserNamed0(): void {
445450
'loginAutocomplete',
446451
true
447452
],
453+
[
454+
'loginCanRememberme',
455+
false
456+
],
448457
[],
449458
[
450459
'loginResetPasswordLink',
@@ -455,7 +464,7 @@ public function testShowLoginFormForUserNamed0(): void {
455464
false
456465
],
457466
];
458-
$this->initialState->expects($this->exactly(13))
467+
$this->initialState->expects($this->exactly(14))
459468
->method('provideInitialState')
460469
->willReturnCallback(function () use (&$calls): void {
461470
$expected = array_shift($calls);
@@ -476,7 +485,19 @@ public function testShowLoginFormForUserNamed0(): void {
476485
$this->assertEquals($expectedResponse, $this->loginController->showLoginForm('0', ''));
477486
}
478487

479-
public function testLoginWithInvalidCredentials(): void {
488+
public static function remembermeProvider(): array {
489+
return [
490+
[
491+
true,
492+
],
493+
[
494+
false,
495+
],
496+
];
497+
}
498+
499+
#[DataProvider('remembermeProvider')]
500+
public function testLoginWithInvalidCredentials(bool $rememberme): void {
480501
$user = 'MyUserName';
481502
$password = 'secret';
482503
$loginPageUrl = '/login?redirect_url=/apps/files';
@@ -491,6 +512,7 @@ public function testLoginWithInvalidCredentials(): void {
491512
$this->request,
492513
$user,
493514
$password,
515+
$rememberme,
494516
'/apps/files'
495517
);
496518
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
@@ -509,12 +531,13 @@ public function testLoginWithInvalidCredentials(): void {
509531
$expected = new RedirectResponse($loginPageUrl);
510532
$expected->throttle(['user' => 'MyUserName']);
511533

512-
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, '/apps/files');
534+
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme, '/apps/files');
513535

514536
$this->assertEquals($expected, $response);
515537
}
516538

517-
public function testLoginWithValidCredentials(): void {
539+
#[DataProvider('remembermeProvider')]
540+
public function testLoginWithValidCredentials(bool $rememberme): void {
518541
$user = 'MyUserName';
519542
$password = 'secret';
520543
$loginChain = $this->createMock(LoginChain::class);
@@ -527,7 +550,8 @@ public function testLoginWithValidCredentials(): void {
527550
$loginData = new LoginData(
528551
$this->request,
529552
$user,
530-
$password
553+
$password,
554+
$rememberme,
531555
);
532556
$loginResult = LoginResult::success($loginData);
533557
$loginChain->expects($this->once())
@@ -540,10 +564,11 @@ public function testLoginWithValidCredentials(): void {
540564
->willReturn('/default/foo');
541565

542566
$expected = new RedirectResponse('/default/foo');
543-
$this->assertEquals($expected, $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password));
567+
$this->assertEquals($expected, $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme));
544568
}
545569

546-
public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn(): void {
570+
#[DataProvider('remembermeProvider')]
571+
public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn(bool $rememberme): void {
547572
/** @var IUser|MockObject $user */
548573
$user = $this->createMock(IUser::class);
549574
$user->expects($this->any())
@@ -567,14 +592,15 @@ public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn(): void {
567592
$this->userSession->expects($this->never())
568593
->method('createRememberMeToken');
569594

570-
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $originalUrl);
595+
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $rememberme, $originalUrl);
571596

572597
$expected = new RedirectResponse('');
573598
$expected->throttle(['user' => 'Jane']);
574599
$this->assertEquals($expected, $response);
575600
}
576601

577-
public function testLoginWithoutPassedCsrfCheckAndLoggedIn(): void {
602+
#[DataProvider('remembermeProvider')]
603+
public function testLoginWithoutPassedCsrfCheckAndLoggedIn(bool $rememberme): void {
578604
/** @var IUser|MockObject $user */
579605
$user = $this->createMock(IUser::class);
580606
$user->expects($this->any())
@@ -607,13 +633,14 @@ public function testLoginWithoutPassedCsrfCheckAndLoggedIn(): void {
607633
->with('remember_login_cookie_lifetime')
608634
->willReturn(1234);
609635

610-
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $originalUrl);
636+
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $rememberme, $originalUrl);
611637

612638
$expected = new RedirectResponse($redirectUrl);
613639
$this->assertEquals($expected, $response);
614640
}
615641

616-
public function testLoginWithValidCredentialsAndRedirectUrl(): void {
642+
#[DataProvider('remembermeProvider')]
643+
public function testLoginWithValidCredentialsAndRedirectUrl(bool $rememberme): void {
617644
$user = 'MyUserName';
618645
$password = 'secret';
619646
$redirectUrl = 'https://next.cloud/apps/mail';
@@ -628,6 +655,7 @@ public function testLoginWithValidCredentialsAndRedirectUrl(): void {
628655
$this->request,
629656
$user,
630657
$password,
658+
$rememberme,
631659
'/apps/mail'
632660
);
633661
$loginResult = LoginResult::success($loginData);
@@ -644,12 +672,13 @@ public function testLoginWithValidCredentialsAndRedirectUrl(): void {
644672
->willReturn($redirectUrl);
645673
$expected = new RedirectResponse($redirectUrl);
646674

647-
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, '/apps/mail');
675+
$response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme, '/apps/mail');
648676

649677
$this->assertEquals($expected, $response);
650678
}
651679

652-
public function testToNotLeakLoginName(): void {
680+
#[DataProvider('remembermeProvider')]
681+
public function testToNotLeakLoginName(bool $rememberme): void {
653682
$loginChain = $this->createMock(LoginChain::class);
654683
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
655684
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
@@ -662,6 +691,7 @@ public function testToNotLeakLoginName(): void {
662691
$this->request,
663692
664693
'just wrong',
694+
$rememberme,
665695
'/apps/files'
666696
);
667697
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
@@ -688,6 +718,7 @@ public function testToNotLeakLoginName(): void {
688718
$trustedDomainHelper,
689719
690720
'just wrong',
721+
$rememberme,
691722
'/apps/files'
692723
);
693724

tests/lib/Authentication/Login/ALoginTestCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ protected function getLoggedInLoginDataWithRedirectUrl(): LoginData {
8383
$this->request,
8484
$this->username,
8585
$this->password,
86+
true,
8687
$this->redirectUrl
8788
);
8889
$data->setUser($this->user);
@@ -94,6 +95,7 @@ protected function getLoggedInLoginDataWithTimezone(): LoginData {
9495
$this->request,
9596
$this->username,
9697
$this->password,
98+
true,
9799
null,
98100
$this->timezone,
99101
$this->timeZoneOffset

0 commit comments

Comments
 (0)