From 373c1a2e0968cee9de410495e95f894d135ebbbb Mon Sep 17 00:00:00 2001 From: silver Date: Tue, 8 Sep 2026 16:55:34 +0200 Subject: [PATCH 1/2] fix(auth): check reset link expiry against interactive login timestamp Signed-off-by: silver Assisted-by: ClaudeCode:claude-fable-5 --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../VerificationToken/VerificationToken.php | 4 +- lib/private/Server.php | 2 + lib/private/User/LastInteractiveLogin.php | 50 ++++++++++ lib/private/User/Session.php | 5 + .../VerificationTokenTest.php | 59 +++++++++--- tests/lib/User/SessionTest.php | 94 ++++++++++++++----- 8 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 lib/private/User/LastInteractiveLogin.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 4467383ba6a9b..0cca32ecd89e5 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -2428,6 +2428,7 @@ 'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php', 'OC\\User\\DisabledUserException' => $baseDir . '/lib/private/User/DisabledUserException.php', 'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php', + 'OC\\User\\LastInteractiveLogin' => $baseDir . '/lib/private/User/LastInteractiveLogin.php', 'OC\\User\\LazyUser' => $baseDir . '/lib/private/User/LazyUser.php', 'OC\\User\\Listeners\\BeforeUserDeletedListener' => $baseDir . '/lib/private/User/Listeners/BeforeUserDeletedListener.php', 'OC\\User\\Listeners\\UserChangedListener' => $baseDir . '/lib/private/User/Listeners/UserChangedListener.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 27b13860aec74..0f0c245665b00 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -2469,6 +2469,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php', 'OC\\User\\DisabledUserException' => __DIR__ . '/../../..' . '/lib/private/User/DisabledUserException.php', 'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php', + 'OC\\User\\LastInteractiveLogin' => __DIR__ . '/../../..' . '/lib/private/User/LastInteractiveLogin.php', 'OC\\User\\LazyUser' => __DIR__ . '/../../..' . '/lib/private/User/LazyUser.php', 'OC\\User\\Listeners\\BeforeUserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/BeforeUserDeletedListener.php', 'OC\\User\\Listeners\\UserChangedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/UserChangedListener.php', diff --git a/lib/private/Security/VerificationToken/VerificationToken.php b/lib/private/Security/VerificationToken/VerificationToken.php index 13eec2b95bfbf..d03e52996ab41 100644 --- a/lib/private/Security/VerificationToken/VerificationToken.php +++ b/lib/private/Security/VerificationToken/VerificationToken.php @@ -8,6 +8,7 @@ namespace OC\Security\VerificationToken; +use OC\User\LastInteractiveLogin; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IConfig; @@ -27,6 +28,7 @@ public function __construct( private ITimeFactory $timeFactory, private ISecureRandom $secureRandom, private IJobList $jobList, + private LastInteractiveLogin $lastInteractiveLogin, ) { } @@ -71,7 +73,7 @@ public function check( } if ($splitToken[0] < ($this->timeFactory->getTime() - self::TOKEN_LIFETIME) - || ($expiresWithLogin && $user->getLastLogin() > $splitToken[0])) { + || ($expiresWithLogin && $this->lastInteractiveLogin->get($user) > $splitToken[0])) { $this->throwInvalidTokenException(InvalidTokenException::TOKEN_EXPIRED); } diff --git a/lib/private/Server.php b/lib/private/Server.php index ab4f6c93cb68a..b0218282bef5a 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -152,6 +152,7 @@ use OC\Translation\TranslationManager; use OC\User\AvailabilityCoordinator; use OC\User\DisplayNameCache; +use OC\User\LastInteractiveLogin; use OC\User\Listeners\BeforeUserDeletedListener; use OC\User\Listeners\UserChangedListener; use OC\User\Session; @@ -449,6 +450,7 @@ public function __construct( $c->get(ILockdownManager::class), $c->get(LoggerInterface::class), $c->get(IEventDispatcher::class), + $c->get(LastInteractiveLogin::class), ); /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password): void { diff --git a/lib/private/User/LastInteractiveLogin.php b/lib/private/User/LastInteractiveLogin.php new file mode 100644 index 0000000000000..1711fffafc382 --- /dev/null +++ b/lib/private/User/LastInteractiveLogin.php @@ -0,0 +1,50 @@ +userConfig->setValueInt( + $user->getUID(), + self::CONFIG_APP, + self::CONFIG_KEY, + $this->timeFactory->getTime(), + ); + } + + public function get(IUser $user): int { + return $this->userConfig->getValueInt( + $user->getUID(), + self::CONFIG_APP, + self::CONFIG_KEY, + ); + } +} diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 578f8e75b9e25..27630aea832b6 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -82,6 +82,7 @@ public function __construct( private ILockdownManager $lockdownManager, private LoggerInterface $logger, private IEventDispatcher $dispatcher, + private LastInteractiveLogin $lastInteractiveLogin, ) { } @@ -371,6 +372,9 @@ public function completeLogin(IUser $user, array $loginDetails, $regenerateSessi $isToken, ]); if ($this->isLoggedIn()) { + if (!$isToken) { + $this->lastInteractiveLogin->record($user); + } $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); return true; } @@ -963,6 +967,7 @@ public function loginWithCookie($uid, $currentToken, $oldSessionId) { $this->setToken($token->getId()); $this->lockdownManager->setToken($token); $user->updateLastLoginTimestamp(); + $this->lastInteractiveLogin->record($user); $password = null; try { $password = $this->tokenProvider->getPassword($token, $sessionId); diff --git a/tests/lib/Security/VerificationToken/VerificationTokenTest.php b/tests/lib/Security/VerificationToken/VerificationTokenTest.php index ed5890afba5a0..7976580617fe6 100644 --- a/tests/lib/Security/VerificationToken/VerificationTokenTest.php +++ b/tests/lib/Security/VerificationToken/VerificationTokenTest.php @@ -10,6 +10,7 @@ namespace Test\Security\VerificationToken; use OC\Security\VerificationToken\VerificationToken; +use OC\User\LastInteractiveLogin; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IConfig; @@ -33,6 +34,8 @@ class VerificationTokenTest extends TestCase { protected $timeFactory; /** @var IJobList|MockObject */ protected $jobList; + /** @var LastInteractiveLogin|MockObject */ + protected $lastInteractiveLogin; #[\Override] protected function setUp(): void { @@ -43,16 +46,24 @@ protected function setUp(): void { $this->timeFactory = $this->createMock(ITimeFactory::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->jobList = $this->createMock(IJobList::class); + $this->lastInteractiveLogin = $this->createMock(LastInteractiveLogin::class); $this->token = new VerificationToken( $this->config, $this->crypto, $this->timeFactory, $this->secureRandom, - $this->jobList + $this->jobList, + $this->lastInteractiveLogin ); } + protected function mockLastInteractiveLogin(int $timestamp): void { + $this->lastInteractiveLogin->expects($this->atLeastOnce()) + ->method('get') + ->willReturn($timestamp); + } + public function testTokenUserUnknown(): void { $this->expectException(InvalidTokenException::class); $this->expectExceptionCode(InvalidTokenException::USER_UNKNOWN); @@ -148,9 +159,6 @@ public function testTokenExpired(): void { $user->expects($this->atLeastOnce()) ->method('getUID') ->willReturn('alice'); - $user->expects($this->any()) - ->method('getLastLogin') - ->willReturn(604803); $this->config->expects($this->atLeastOnce()) ->method('getUserValue') @@ -182,9 +190,7 @@ public function testTokenExpiredByLogin(): void { $user->expects($this->atLeastOnce()) ->method('getUID') ->willReturn('alice'); - $user->expects($this->any()) - ->method('getLastLogin') - ->willReturn(604803); + $this->mockLastInteractiveLogin(604803); $this->config->expects($this->atLeastOnce()) ->method('getUserValue') @@ -208,6 +214,39 @@ public function testTokenExpiredByLogin(): void { $this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar', true); } + public function testTokenNotExpiredBySessionRevalidation(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->atLeastOnce()) + ->method('isEnabled') + ->willReturn(true); + $user->expects($this->atLeastOnce()) + ->method('getUID') + ->willReturn('alice'); + $user->expects($this->never()) + ->method('getLastLogin'); + // last actual authentication predates the token + $this->mockLastInteractiveLogin(604700); + + $this->config->expects($this->atLeastOnce()) + ->method('getUserValue') + ->with('alice', 'core', 'fingerprintToken', null) + ->willReturn('encryptedToken'); + $this->config->expects($this->any()) + ->method('getSystemValueString') + ->with('secret') + ->willReturn('357111317'); + + $this->crypto->method('decrypt') + ->with('encryptedToken', 'foobar' . '357111317') + ->willReturn('604800:barfoo'); + + $this->timeFactory->expects($this->any()) + ->method('getTime') + ->willReturn(604801); + + $this->token->check('barfoo', $user, 'fingerprintToken', 'foobar', true); + } + public function testTokenMismatch(): void { $user = $this->createMock(IUser::class); $user->expects($this->atLeastOnce()) @@ -216,9 +255,6 @@ public function testTokenMismatch(): void { $user->expects($this->atLeastOnce()) ->method('getUID') ->willReturn('alice'); - $user->expects($this->any()) - ->method('getLastLogin') - ->willReturn(604703); $this->config->expects($this->atLeastOnce()) ->method('getUserValue') @@ -250,9 +286,6 @@ public function testTokenSuccess(): void { $user->expects($this->atLeastOnce()) ->method('getUID') ->willReturn('alice'); - $user->expects($this->any()) - ->method('getLastLogin') - ->willReturn(604703); $this->config->expects($this->atLeastOnce()) ->method('getUserValue') diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index f466569eb9b3c..a8e8a881df798 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -18,6 +18,7 @@ use OC\Authentication\Token\PublicKeyToken; use OC\Security\CSRF\CsrfTokenManager; use OC\Session\Memory; +use OC\User\LastInteractiveLogin; use OC\User\LoginException; use OC\User\Manager; use OC\User\Session; @@ -68,6 +69,8 @@ class SessionTest extends \Test\TestCase { private $logger; /** @var IEventDispatcher|MockObject */ private $dispatcher; + /** @var LastInteractiveLogin|MockObject */ + private $lastInteractiveLogin; #[\Override] protected function setUp(): void { @@ -86,6 +89,7 @@ protected function setUp(): void { $this->lockdownManager = $this->createMock(ILockdownManager::class); $this->logger = $this->createMock(LoggerInterface::class); $this->dispatcher = $this->createMock(IEventDispatcher::class); + $this->lastInteractiveLogin = $this->createMock(LastInteractiveLogin::class); $this->userSession = $this->getMockBuilder(Session::class) ->setConstructorArgs([ $this->manager, @@ -96,7 +100,8 @@ protected function setUp(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher + $this->dispatcher, + $this->lastInteractiveLogin ]) ->onlyMethods([ 'setMagicInCookie', @@ -120,7 +125,7 @@ public function testIsLoggedIn($isLoggedIn): void { $manager = $this->createMock(Manager::class); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods([ 'getUser' ]) @@ -147,7 +152,7 @@ public function testSetUser(): void { ->method('getUID') ->willReturn('foo'); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $userSession->setUser($user); } @@ -198,13 +203,17 @@ public function testLoginValidPasswordEnabled(): void { $user->expects($this->once()) ->method('updateLastLoginTimestamp'); + $this->lastInteractiveLogin->expects($this->once()) + ->method('record') + ->with($user); + $manager->expects($this->once()) ->method('checkPasswordNoLogging') ->with('foo', 'bar') ->willReturn($user); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods([ 'prepareUserLogin' ]) @@ -226,6 +235,37 @@ public function testLoginValidPasswordEnabled(): void { $this->assertEquals($user, $userSession->getUser()); } + public function testCompleteLoginWithTokenDoesNotRecordInteractiveLogin(): void { + $manager = $this->createMock(Manager::class); + $session = $this->createMock(ISession::class); + + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('foo'); + + $this->lastInteractiveLogin->expects($this->never()) + ->method('record'); + + $userSession = $this->getMockBuilder(Session::class) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->onlyMethods(['prepareUserLogin']) + ->getMock(); + + $this->assertTrue($userSession->completeLogin( + $user, + [ + 'loginName' => 'foo', + 'password' => 'bar', + 'token' => $this->createMock(IToken::class), + ], + false + )); + } + public function testLoginValidPasswordDisabled(): void { $this->expectException(LoginException::class); @@ -267,7 +307,7 @@ public function testLoginValidPasswordDisabled(): void { $this->dispatcher->expects($this->never()) ->method('dispatch'); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $userSession->login('foo', 'bar'); } @@ -286,7 +326,7 @@ public function testLoginInvalidPassword(): void { ]) ->getMock(); $backend = $this->createMock(\Test\Util\User\Dummy::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $user = $this->createMock(IUser::class); @@ -329,7 +369,7 @@ public function testPasswordlessLoginNoLastCheckUpdate(): void { $this->createMock(LoggerInterface::class), ]) ->getMock(); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('foo'); @@ -373,7 +413,7 @@ public function testLoginLastCheckUpdate(): void { $this->createMock(LoggerInterface::class), ]) ->getMock(); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('foo'); @@ -406,7 +446,7 @@ public function testLoginLastCheckUpdate(): void { public function testLoginNonExisting(): void { $session = $this->createMock(Memory::class); $manager = $this->createMock(Manager::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $session->expects($this->never()) ->method('set'); @@ -434,7 +474,7 @@ public function testLogClientInNoTokenPasswordWith2fa(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -479,7 +519,7 @@ public function testLogClientInUnexist(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -505,7 +545,7 @@ public function testLogClientInWithTokenPassword(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -547,7 +587,7 @@ public function testLogClientInNoTokenPasswordNo2fa(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'isTwoFactorEnforced']) ->getMock(); @@ -750,7 +790,7 @@ public function testRememberLoginValidToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie', 'setLoginName']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->getMock(); $user = $this->createMock(IUser::class); @@ -846,7 +886,7 @@ public function testRememberLoginInvalidSessionToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->getMock(); $user = $this->createMock(IUser::class); @@ -920,7 +960,7 @@ public function testRememberLoginInvalidToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->getMock(); $user = $this->createMock(IUser::class); @@ -973,7 +1013,7 @@ public function testRememberLoginInvalidUser(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->getMock(); $token = 'goodToken'; $oldSessionId = 'sess321'; @@ -1021,7 +1061,7 @@ public function testActiveUserAfterSetSession(): void { $session = new Memory(); $session->set('user_id', 'foo'); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods([ 'validateSession' ]) @@ -1041,7 +1081,7 @@ public function testCreateSessionToken(): void { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1082,7 +1122,7 @@ public function testCreateRememberedSessionToken(): void { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1126,7 +1166,7 @@ public function testCreateSessionTokenWithTokenPassword(): void { $session = $this->createMock(ISession::class); $token = $this->createMock(IToken::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1173,7 +1213,7 @@ public function testCreateSessionTokenWithNonExistentUser(): void { ->disableOriginalConstructor() ->getMock(); $session = $this->createMock(ISession::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); $request = $this->createMock(IRequest::class); $uid = 'user123'; @@ -1246,7 +1286,8 @@ public function testTryBasicAuthLoginValid(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher + $this->dispatcher, + $this->lastInteractiveLogin ]) ->onlyMethods([ 'logClientIn', @@ -1297,7 +1338,8 @@ public function testTryBasicAuthLoginNoLogin(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher + $this->dispatcher, + $this->lastInteractiveLogin ]) ->onlyMethods([ 'logClientIn', @@ -1326,7 +1368,7 @@ public function testLogClientInThrottlerUsername(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -1373,7 +1415,7 @@ public function testLogClientInThrottlerEmail(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); From 8ba768f214970992fb76823a5e6db41d7fa11906 Mon Sep 17 00:00:00 2001 From: silver Date: Tue, 15 Sep 2026 15:08:08 +0200 Subject: [PATCH 2/2] refactor(auth): record interactive login only for browser logins Signed-off-by: silver Assisted-by: ClaudeCode:claude-opus-5 --- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + lib/private/Authentication/Login/Chain.php | 2 + .../Login/RecordInteractiveLoginCommand.php | 26 +++++ lib/private/Server.php | 4 +- lib/private/User/LastInteractiveLogin.php | 2 + .../UserLoggedInWithCookieListener.php | 34 +++++++ lib/private/User/Session.php | 5 - .../RecordInteractiveLoginCommandTest.php | 41 ++++++++ .../UserLoggedInWithCookieListenerTest.php | 49 ++++++++++ tests/lib/User/SessionTest.php | 94 +++++-------------- 11 files changed, 186 insertions(+), 75 deletions(-) create mode 100644 lib/private/Authentication/Login/RecordInteractiveLoginCommand.php create mode 100644 lib/private/User/Listeners/UserLoggedInWithCookieListener.php create mode 100644 tests/lib/Authentication/Login/RecordInteractiveLoginCommandTest.php create mode 100644 tests/lib/User/Listeners/UserLoggedInWithCookieListenerTest.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 0cca32ecd89e5..c9c5999a67073 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1317,6 +1317,7 @@ 'OC\\Authentication\\Login\\LoginData' => $baseDir . '/lib/private/Authentication/Login/LoginData.php', 'OC\\Authentication\\Login\\LoginResult' => $baseDir . '/lib/private/Authentication/Login/LoginResult.php', 'OC\\Authentication\\Login\\PreLoginHookCommand' => $baseDir . '/lib/private/Authentication/Login/PreLoginHookCommand.php', + 'OC\\Authentication\\Login\\RecordInteractiveLoginCommand' => $baseDir . '/lib/private/Authentication/Login/RecordInteractiveLoginCommand.php', 'OC\\Authentication\\Login\\SetUserTimezoneCommand' => $baseDir . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php', 'OC\\Authentication\\Login\\TwoFactorCommand' => $baseDir . '/lib/private/Authentication/Login/TwoFactorCommand.php', 'OC\\Authentication\\Login\\UidLoginCommand' => $baseDir . '/lib/private/Authentication/Login/UidLoginCommand.php', @@ -2432,6 +2433,7 @@ 'OC\\User\\LazyUser' => $baseDir . '/lib/private/User/LazyUser.php', 'OC\\User\\Listeners\\BeforeUserDeletedListener' => $baseDir . '/lib/private/User/Listeners/BeforeUserDeletedListener.php', 'OC\\User\\Listeners\\UserChangedListener' => $baseDir . '/lib/private/User/Listeners/UserChangedListener.php', + 'OC\\User\\Listeners\\UserLoggedInWithCookieListener' => $baseDir . '/lib/private/User/Listeners/UserLoggedInWithCookieListener.php', 'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php', 'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php', 'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0f0c245665b00..269732794359d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1358,6 +1358,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Authentication\\Login\\LoginData' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginData.php', 'OC\\Authentication\\Login\\LoginResult' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginResult.php', 'OC\\Authentication\\Login\\PreLoginHookCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/PreLoginHookCommand.php', + 'OC\\Authentication\\Login\\RecordInteractiveLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/RecordInteractiveLoginCommand.php', 'OC\\Authentication\\Login\\SetUserTimezoneCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php', 'OC\\Authentication\\Login\\TwoFactorCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/TwoFactorCommand.php', 'OC\\Authentication\\Login\\UidLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UidLoginCommand.php', @@ -2473,6 +2474,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\User\\LazyUser' => __DIR__ . '/../../..' . '/lib/private/User/LazyUser.php', 'OC\\User\\Listeners\\BeforeUserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/BeforeUserDeletedListener.php', 'OC\\User\\Listeners\\UserChangedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/UserChangedListener.php', + 'OC\\User\\Listeners\\UserLoggedInWithCookieListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/UserLoggedInWithCookieListener.php', 'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php', 'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php', 'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php index 50be4ecb56c1a..9256db263b978 100644 --- a/lib/private/Authentication/Login/Chain.php +++ b/lib/private/Authentication/Login/Chain.php @@ -18,6 +18,7 @@ public function __construct( private CompleteLoginCommand $completeLoginCommand, private CreateSessionTokenCommand $createSessionTokenCommand, private ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, + private RecordInteractiveLoginCommand $recordInteractiveLoginCommand, private UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, private SetUserTimezoneCommand $setUserTimezoneCommand, private TwoFactorCommand $twoFactorCommand, @@ -34,6 +35,7 @@ public function process(LoginData $loginData): LoginResult { ->setNext($this->completeLoginCommand) ->setNext($this->createSessionTokenCommand) ->setNext($this->clearLostPasswordTokensCommand) + ->setNext($this->recordInteractiveLoginCommand) ->setNext($this->updateLastPasswordConfirmCommand) ->setNext($this->setUserTimezoneCommand) ->setNext($this->twoFactorCommand) diff --git a/lib/private/Authentication/Login/RecordInteractiveLoginCommand.php b/lib/private/Authentication/Login/RecordInteractiveLoginCommand.php new file mode 100644 index 0000000000000..40ceb5a03d328 --- /dev/null +++ b/lib/private/Authentication/Login/RecordInteractiveLoginCommand.php @@ -0,0 +1,26 @@ +lastInteractiveLogin->record($loginData->getUser()); + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index b0218282bef5a..cd9cbb124307b 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -152,9 +152,9 @@ use OC\Translation\TranslationManager; use OC\User\AvailabilityCoordinator; use OC\User\DisplayNameCache; -use OC\User\LastInteractiveLogin; use OC\User\Listeners\BeforeUserDeletedListener; use OC\User\Listeners\UserChangedListener; +use OC\User\Listeners\UserLoggedInWithCookieListener; use OC\User\Session; use OC\User\User; use OCA\Theming\ImageManager; @@ -450,7 +450,6 @@ public function __construct( $c->get(ILockdownManager::class), $c->get(LoggerInterface::class), $c->get(IEventDispatcher::class), - $c->get(LastInteractiveLogin::class), ); /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password): void { @@ -1179,6 +1178,7 @@ private function connectDispatcher(): void { $eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); $eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class); $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class); + $eventDispatcher->addServiceListener(UserLoggedInWithCookieEvent::class, UserLoggedInWithCookieListener::class); FilesMetadataManager::loadListeners($eventDispatcher); GenerateBlurhashMetadata::loadListeners($eventDispatcher); diff --git a/lib/private/User/LastInteractiveLogin.php b/lib/private/User/LastInteractiveLogin.php index 1711fffafc382..dc84c3d448747 100644 --- a/lib/private/User/LastInteractiveLogin.php +++ b/lib/private/User/LastInteractiveLogin.php @@ -37,6 +37,7 @@ public function record(IUser $user): void { self::CONFIG_APP, self::CONFIG_KEY, $this->timeFactory->getTime(), + lazy: true, ); } @@ -45,6 +46,7 @@ public function get(IUser $user): int { $user->getUID(), self::CONFIG_APP, self::CONFIG_KEY, + lazy: true, ); } } diff --git a/lib/private/User/Listeners/UserLoggedInWithCookieListener.php b/lib/private/User/Listeners/UserLoggedInWithCookieListener.php new file mode 100644 index 0000000000000..024eabe8f0a3d --- /dev/null +++ b/lib/private/User/Listeners/UserLoggedInWithCookieListener.php @@ -0,0 +1,34 @@ + + */ +class UserLoggedInWithCookieListener implements IEventListener { + public function __construct( + private LastInteractiveLogin $lastInteractiveLogin, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if (!($event instanceof UserLoggedInWithCookieEvent)) { + return; + } + + $this->lastInteractiveLogin->record($event->getUser()); + } +} diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 27630aea832b6..578f8e75b9e25 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -82,7 +82,6 @@ public function __construct( private ILockdownManager $lockdownManager, private LoggerInterface $logger, private IEventDispatcher $dispatcher, - private LastInteractiveLogin $lastInteractiveLogin, ) { } @@ -372,9 +371,6 @@ public function completeLogin(IUser $user, array $loginDetails, $regenerateSessi $isToken, ]); if ($this->isLoggedIn()) { - if (!$isToken) { - $this->lastInteractiveLogin->record($user); - } $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); return true; } @@ -967,7 +963,6 @@ public function loginWithCookie($uid, $currentToken, $oldSessionId) { $this->setToken($token->getId()); $this->lockdownManager->setToken($token); $user->updateLastLoginTimestamp(); - $this->lastInteractiveLogin->record($user); $password = null; try { $password = $this->tokenProvider->getPassword($token, $sessionId); diff --git a/tests/lib/Authentication/Login/RecordInteractiveLoginCommandTest.php b/tests/lib/Authentication/Login/RecordInteractiveLoginCommandTest.php new file mode 100644 index 0000000000000..7844a41eab549 --- /dev/null +++ b/tests/lib/Authentication/Login/RecordInteractiveLoginCommandTest.php @@ -0,0 +1,41 @@ +lastInteractiveLogin = $this->createMock(LastInteractiveLogin::class); + + $this->cmd = new RecordInteractiveLoginCommand( + $this->lastInteractiveLogin + ); + } + + public function testProcess(): void { + $data = $this->getLoggedInLoginData(); + $this->lastInteractiveLogin->expects($this->once()) + ->method('record') + ->with($this->user); + + $result = $this->cmd->process($data); + + $this->assertTrue($result->isSuccess()); + } +} diff --git a/tests/lib/User/Listeners/UserLoggedInWithCookieListenerTest.php b/tests/lib/User/Listeners/UserLoggedInWithCookieListenerTest.php new file mode 100644 index 0000000000000..ed5f0fb89a328 --- /dev/null +++ b/tests/lib/User/Listeners/UserLoggedInWithCookieListenerTest.php @@ -0,0 +1,49 @@ +lastInteractiveLogin = $this->createMock(LastInteractiveLogin::class); + $this->listener = new UserLoggedInWithCookieListener($this->lastInteractiveLogin); + } + + public function testRecordsRememberMeLogin(): void { + $user = $this->createMock(IUser::class); + $this->lastInteractiveLogin->expects($this->once()) + ->method('record') + ->with($user); + + $this->listener->handle(new UserLoggedInWithCookieEvent($user, null)); + } + + public function testIgnoresUnrelatedEvent(): void { + $user = $this->createMock(IUser::class); + $this->lastInteractiveLogin->expects($this->never()) + ->method('record'); + + $this->listener->handle(new UserLoggedInEvent($user, 'user', null, false)); + } +} diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index a8e8a881df798..f466569eb9b3c 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -18,7 +18,6 @@ use OC\Authentication\Token\PublicKeyToken; use OC\Security\CSRF\CsrfTokenManager; use OC\Session\Memory; -use OC\User\LastInteractiveLogin; use OC\User\LoginException; use OC\User\Manager; use OC\User\Session; @@ -69,8 +68,6 @@ class SessionTest extends \Test\TestCase { private $logger; /** @var IEventDispatcher|MockObject */ private $dispatcher; - /** @var LastInteractiveLogin|MockObject */ - private $lastInteractiveLogin; #[\Override] protected function setUp(): void { @@ -89,7 +86,6 @@ protected function setUp(): void { $this->lockdownManager = $this->createMock(ILockdownManager::class); $this->logger = $this->createMock(LoggerInterface::class); $this->dispatcher = $this->createMock(IEventDispatcher::class); - $this->lastInteractiveLogin = $this->createMock(LastInteractiveLogin::class); $this->userSession = $this->getMockBuilder(Session::class) ->setConstructorArgs([ $this->manager, @@ -100,8 +96,7 @@ protected function setUp(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher, - $this->lastInteractiveLogin + $this->dispatcher ]) ->onlyMethods([ 'setMagicInCookie', @@ -125,7 +120,7 @@ public function testIsLoggedIn($isLoggedIn): void { $manager = $this->createMock(Manager::class); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods([ 'getUser' ]) @@ -152,7 +147,7 @@ public function testSetUser(): void { ->method('getUID') ->willReturn('foo'); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $userSession->setUser($user); } @@ -203,17 +198,13 @@ public function testLoginValidPasswordEnabled(): void { $user->expects($this->once()) ->method('updateLastLoginTimestamp'); - $this->lastInteractiveLogin->expects($this->once()) - ->method('record') - ->with($user); - $manager->expects($this->once()) ->method('checkPasswordNoLogging') ->with('foo', 'bar') ->willReturn($user); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods([ 'prepareUserLogin' ]) @@ -235,37 +226,6 @@ public function testLoginValidPasswordEnabled(): void { $this->assertEquals($user, $userSession->getUser()); } - public function testCompleteLoginWithTokenDoesNotRecordInteractiveLogin(): void { - $manager = $this->createMock(Manager::class); - $session = $this->createMock(ISession::class); - - $user = $this->createMock(IUser::class); - $user->expects($this->any()) - ->method('isEnabled') - ->willReturn(true); - $user->expects($this->any()) - ->method('getUID') - ->willReturn('foo'); - - $this->lastInteractiveLogin->expects($this->never()) - ->method('record'); - - $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) - ->onlyMethods(['prepareUserLogin']) - ->getMock(); - - $this->assertTrue($userSession->completeLogin( - $user, - [ - 'loginName' => 'foo', - 'password' => 'bar', - 'token' => $this->createMock(IToken::class), - ], - false - )); - } - public function testLoginValidPasswordDisabled(): void { $this->expectException(LoginException::class); @@ -307,7 +267,7 @@ public function testLoginValidPasswordDisabled(): void { $this->dispatcher->expects($this->never()) ->method('dispatch'); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $userSession->login('foo', 'bar'); } @@ -326,7 +286,7 @@ public function testLoginInvalidPassword(): void { ]) ->getMock(); $backend = $this->createMock(\Test\Util\User\Dummy::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $user = $this->createMock(IUser::class); @@ -369,7 +329,7 @@ public function testPasswordlessLoginNoLastCheckUpdate(): void { $this->createMock(LoggerInterface::class), ]) ->getMock(); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('foo'); @@ -413,7 +373,7 @@ public function testLoginLastCheckUpdate(): void { $this->createMock(LoggerInterface::class), ]) ->getMock(); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('foo'); @@ -446,7 +406,7 @@ public function testLoginLastCheckUpdate(): void { public function testLoginNonExisting(): void { $session = $this->createMock(Memory::class); $manager = $this->createMock(Manager::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $session->expects($this->never()) ->method('set'); @@ -474,7 +434,7 @@ public function testLogClientInNoTokenPasswordWith2fa(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -519,7 +479,7 @@ public function testLogClientInUnexist(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -545,7 +505,7 @@ public function testLogClientInWithTokenPassword(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -587,7 +547,7 @@ public function testLogClientInNoTokenPasswordNo2fa(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'isTwoFactorEnforced']) ->getMock(); @@ -790,7 +750,7 @@ public function testRememberLoginValidToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie', 'setLoginName']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->getMock(); $user = $this->createMock(IUser::class); @@ -886,7 +846,7 @@ public function testRememberLoginInvalidSessionToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->getMock(); $user = $this->createMock(IUser::class); @@ -960,7 +920,7 @@ public function testRememberLoginInvalidToken(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->getMock(); $user = $this->createMock(IUser::class); @@ -1013,7 +973,7 @@ public function testRememberLoginInvalidUser(): void { $userSession = $this->getMockBuilder(Session::class) //override, otherwise tests will fail because of setcookie() ->onlyMethods(['setMagicInCookie']) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->getMock(); $token = 'goodToken'; $oldSessionId = 'sess321'; @@ -1061,7 +1021,7 @@ public function testActiveUserAfterSetSession(): void { $session = new Memory(); $session->set('user_id', 'foo'); $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods([ 'validateSession' ]) @@ -1081,7 +1041,7 @@ public function testCreateSessionToken(): void { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1122,7 +1082,7 @@ public function testCreateRememberedSessionToken(): void { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1166,7 +1126,7 @@ public function testCreateSessionTokenWithTokenPassword(): void { $session = $this->createMock(ISession::class); $token = $this->createMock(IToken::class); $user = $this->createMock(IUser::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $requestId = $this->createMock(IRequestId::class); $config = $this->createMock(IConfig::class); @@ -1213,7 +1173,7 @@ public function testCreateSessionTokenWithNonExistentUser(): void { ->disableOriginalConstructor() ->getMock(); $session = $this->createMock(ISession::class); - $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin); + $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher); $request = $this->createMock(IRequest::class); $uid = 'user123'; @@ -1286,8 +1246,7 @@ public function testTryBasicAuthLoginValid(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher, - $this->lastInteractiveLogin + $this->dispatcher ]) ->onlyMethods([ 'logClientIn', @@ -1338,8 +1297,7 @@ public function testTryBasicAuthLoginNoLogin(): void { $this->random, $this->lockdownManager, $this->logger, - $this->dispatcher, - $this->lastInteractiveLogin + $this->dispatcher ]) ->onlyMethods([ 'logClientIn', @@ -1368,7 +1326,7 @@ public function testLogClientInThrottlerUsername(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); @@ -1415,7 +1373,7 @@ public function testLogClientInThrottlerEmail(): void { /** @var Session $userSession */ $userSession = $this->getMockBuilder(Session::class) - ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher, $this->lastInteractiveLogin]) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock();