diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index 5a21d27898f..917bc061563 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -143,6 +143,11 @@ class LoginController extends Controller {
$this->config->getSystemValue('login_form_autocomplete', true) === true
);
+ $this->initialState->provideInitialState(
+ 'loginCanRememberme',
+ $this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) > 0
+ );
+
if (!empty($redirect_url)) {
[$url, ] = explode('?', $redirect_url);
if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
@@ -287,6 +292,7 @@ class LoginController extends Controller {
ITrustedDomainHelper $trustedDomainHelper,
string $user = '',
string $password = '',
+ bool $rememberme = false,
?string $redirect_url = null,
string $timezone = '',
string $timezone_offset = '',
@@ -339,9 +345,10 @@ class LoginController extends Controller {
$this->request,
$user,
$password,
+ $rememberme,
$redirect_url,
$timezone,
- $timezone_offset
+ $timezone_offset,
);
$result = $loginChain->process($data);
if (!$result->isSuccess()) {
diff --git a/core/src/components/login/LoginForm.vue b/core/src/components/login/LoginForm.vue
index caf95b245ac..e779b2e6176 100644
--- a/core/src/components/login/LoginForm.vue
+++ b/core/src/components/login/LoginForm.vue
@@ -84,6 +84,17 @@
data-login-form-input-password
required />
+
+ {{ t('core', 'Remember me') }}
+
+
config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) === 0) {
$loginData->setRememberLogin(false);
+ }
+ if ($loginData->isRememberLogin()) {
+ $tokenType = IToken::REMEMBER;
+ } else {
$tokenType = IToken::DO_NOT_REMEMBER;
}
diff --git a/lib/private/Authentication/Login/LoginData.php b/lib/private/Authentication/Login/LoginData.php
index 2567da9b340..8791b00413b 100644
--- a/lib/private/Authentication/Login/LoginData.php
+++ b/lib/private/Authentication/Login/LoginData.php
@@ -16,12 +16,11 @@ class LoginData {
/** @var IUser|false|null */
private $user = null;
- private bool $rememberLogin = true;
-
public function __construct(
private IRequest $request,
private string $username,
private ?string $password,
+ private bool $rememberLogin = true,
private ?string $redirectUrl = null,
private string $timeZone = '',
private string $timeZoneOffset = '',
diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php
index 18baaf5b08c..b4fd95513df 100644
--- a/tests/Core/Controller/LoginControllerTest.php
+++ b/tests/Core/Controller/LoginControllerTest.php
@@ -31,6 +31,7 @@ use OCP\IUserManager;
use OCP\Notification\IManager;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\ITrustedDomainHelper;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@@ -277,7 +278,7 @@ class LoginControllerTest extends TestCase {
'',
]
];
- $this->initialState->expects($this->exactly(13))
+ $this->initialState->expects($this->exactly(14))
->method('provideInitialState')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
@@ -309,12 +310,16 @@ class LoginControllerTest extends TestCase {
'loginAutocomplete',
false
],
+ [
+ 'loginCanRememberme',
+ false
+ ],
[
'loginRedirectUrl',
'login/flow'
],
];
- $this->initialState->expects($this->exactly(14))
+ $this->initialState->expects($this->exactly(15))
->method('provideInitialState')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
@@ -351,7 +356,7 @@ class LoginControllerTest extends TestCase {
];
}
- #[\PHPUnit\Framework\Attributes\DataProvider('passwordResetDataProvider')]
+ #[DataProvider('passwordResetDataProvider')]
public function testShowLoginFormWithPasswordResetOption($canChangePassword,
$expectedResult): void {
$this->userSession
@@ -386,13 +391,13 @@ class LoginControllerTest extends TestCase {
'loginUsername',
'LdapUser'
],
- [], [], [],
+ [], [], [], [],
[
'loginCanResetPassword',
$expectedResult
],
];
- $this->initialState->expects($this->exactly(13))
+ $this->initialState->expects($this->exactly(14))
->method('provideInitialState')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
@@ -445,6 +450,10 @@ class LoginControllerTest extends TestCase {
'loginAutocomplete',
true
],
+ [
+ 'loginCanRememberme',
+ false
+ ],
[],
[
'loginResetPasswordLink',
@@ -455,7 +464,7 @@ class LoginControllerTest extends TestCase {
false
],
];
- $this->initialState->expects($this->exactly(13))
+ $this->initialState->expects($this->exactly(14))
->method('provideInitialState')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
@@ -476,7 +485,19 @@ class LoginControllerTest extends TestCase {
$this->assertEquals($expectedResponse, $this->loginController->showLoginForm('0', ''));
}
- public function testLoginWithInvalidCredentials(): void {
+ public static function remembermeProvider(): array {
+ return [
+ [
+ true,
+ ],
+ [
+ false,
+ ],
+ ];
+ }
+
+ #[DataProvider('remembermeProvider')]
+ public function testLoginWithInvalidCredentials(bool $rememberme): void {
$user = 'MyUserName';
$password = 'secret';
$loginPageUrl = '/login?redirect_url=/apps/files';
@@ -491,6 +512,7 @@ class LoginControllerTest extends TestCase {
$this->request,
$user,
$password,
+ $rememberme,
'/apps/files'
);
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
@@ -509,12 +531,13 @@ class LoginControllerTest extends TestCase {
$expected = new RedirectResponse($loginPageUrl);
$expected->throttle(['user' => 'MyUserName']);
- $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, '/apps/files');
+ $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme, '/apps/files');
$this->assertEquals($expected, $response);
}
- public function testLoginWithValidCredentials(): void {
+ #[DataProvider('remembermeProvider')]
+ public function testLoginWithValidCredentials(bool $rememberme): void {
$user = 'MyUserName';
$password = 'secret';
$loginChain = $this->createMock(LoginChain::class);
@@ -527,7 +550,8 @@ class LoginControllerTest extends TestCase {
$loginData = new LoginData(
$this->request,
$user,
- $password
+ $password,
+ $rememberme,
);
$loginResult = LoginResult::success($loginData);
$loginChain->expects($this->once())
@@ -540,10 +564,11 @@ class LoginControllerTest extends TestCase {
->willReturn('/default/foo');
$expected = new RedirectResponse('/default/foo');
- $this->assertEquals($expected, $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password));
+ $this->assertEquals($expected, $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme));
}
- public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn(): void {
+ #[DataProvider('remembermeProvider')]
+ public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn(bool $rememberme): void {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
@@ -567,14 +592,15 @@ class LoginControllerTest extends TestCase {
$this->userSession->expects($this->never())
->method('createRememberMeToken');
- $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $originalUrl);
+ $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $rememberme, $originalUrl);
$expected = new RedirectResponse('');
$expected->throttle(['user' => 'Jane']);
$this->assertEquals($expected, $response);
}
- public function testLoginWithoutPassedCsrfCheckAndLoggedIn(): void {
+ #[DataProvider('remembermeProvider')]
+ public function testLoginWithoutPassedCsrfCheckAndLoggedIn(bool $rememberme): void {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
@@ -607,13 +633,14 @@ class LoginControllerTest extends TestCase {
->with('remember_login_cookie_lifetime')
->willReturn(1234);
- $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $originalUrl);
+ $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, 'Jane', $password, $rememberme, $originalUrl);
$expected = new RedirectResponse($redirectUrl);
$this->assertEquals($expected, $response);
}
- public function testLoginWithValidCredentialsAndRedirectUrl(): void {
+ #[DataProvider('remembermeProvider')]
+ public function testLoginWithValidCredentialsAndRedirectUrl(bool $rememberme): void {
$user = 'MyUserName';
$password = 'secret';
$redirectUrl = 'https://next.cloud/apps/mail';
@@ -628,6 +655,7 @@ class LoginControllerTest extends TestCase {
$this->request,
$user,
$password,
+ $rememberme,
'/apps/mail'
);
$loginResult = LoginResult::success($loginData);
@@ -644,12 +672,13 @@ class LoginControllerTest extends TestCase {
->willReturn($redirectUrl);
$expected = new RedirectResponse($redirectUrl);
- $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, '/apps/mail');
+ $response = $this->loginController->tryLogin($loginChain, $trustedDomainHelper, $user, $password, $rememberme, '/apps/mail');
$this->assertEquals($expected, $response);
}
- public function testToNotLeakLoginName(): void {
+ #[DataProvider('remembermeProvider')]
+ public function testToNotLeakLoginName(bool $rememberme): void {
$loginChain = $this->createMock(LoginChain::class);
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
@@ -662,6 +691,7 @@ class LoginControllerTest extends TestCase {
$this->request,
'john@doe.com',
'just wrong',
+ $rememberme,
'/apps/files'
);
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
@@ -688,6 +718,7 @@ class LoginControllerTest extends TestCase {
$trustedDomainHelper,
'john@doe.com',
'just wrong',
+ $rememberme,
'/apps/files'
);
diff --git a/tests/lib/Authentication/Login/ALoginTestCommand.php b/tests/lib/Authentication/Login/ALoginTestCommand.php
index b955b20beba..da29e74e683 100644
--- a/tests/lib/Authentication/Login/ALoginTestCommand.php
+++ b/tests/lib/Authentication/Login/ALoginTestCommand.php
@@ -83,6 +83,7 @@ abstract class ALoginTestCommand extends TestCase {
$this->request,
$this->username,
$this->password,
+ true,
$this->redirectUrl
);
$data->setUser($this->user);
@@ -94,6 +95,7 @@ abstract class ALoginTestCommand extends TestCase {
$this->request,
$this->username,
$this->password,
+ true,
null,
$this->timezone,
$this->timeZoneOffset