aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/User/SessionTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/User/SessionTest.php')
-rw-r--r--tests/lib/User/SessionTest.php1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
new file mode 100644
index 00000000000..50c449559a0
--- /dev/null
+++ b/tests/lib/User/SessionTest.php
@@ -0,0 +1,1324 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\User;
+
+use OC\AppFramework\Http\Request;
+use OC\Authentication\Events\LoginFailed;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\PublicKeyToken;
+use OC\Security\CSRF\CsrfTokenManager;
+use OC\Session\Memory;
+use OC\User\LoginException;
+use OC\User\Manager;
+use OC\User\Session;
+use OC\User\User;
+use OCA\DAV\Connector\Sabre\Auth;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\IRequestId;
+use OCP\ISession;
+use OCP\IUser;
+use OCP\Lockdown\ILockdownManager;
+use OCP\Security\Bruteforce\IThrottler;
+use OCP\Security\ISecureRandom;
+use OCP\User\Events\PostLoginEvent;
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use function array_diff;
+use function get_class_methods;
+
+/**
+ * @group DB
+ * @package Test\User
+ */
+class SessionTest extends \Test\TestCase {
+ /** @var ITimeFactory|MockObject */
+ private $timeFactory;
+ /** @var IProvider|MockObject */
+ private $tokenProvider;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var IThrottler|MockObject */
+ private $throttler;
+ /** @var ISecureRandom|MockObject */
+ private $random;
+ /** @var Manager|MockObject */
+ private $manager;
+ /** @var ISession|MockObject */
+ private $session;
+ /** @var Session|MockObject */
+ private $userSession;
+ /** @var ILockdownManager|MockObject */
+ private $lockdownManager;
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+ /** @var IEventDispatcher|MockObject */
+ private $dispatcher;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->timeFactory->expects($this->any())
+ ->method('getTime')
+ ->willReturn(10000);
+ $this->tokenProvider = $this->createMock(IProvider::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->throttler = $this->createMock(IThrottler::class);
+ $this->random = $this->createMock(ISecureRandom::class);
+ $this->manager = $this->createMock(Manager::class);
+ $this->session = $this->createMock(ISession::class);
+ $this->lockdownManager = $this->createMock(ILockdownManager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->dispatcher = $this->createMock(IEventDispatcher::class);
+ $this->userSession = $this->getMockBuilder(Session::class)
+ ->setConstructorArgs([
+ $this->manager,
+ $this->session,
+ $this->timeFactory,
+ $this->tokenProvider,
+ $this->config,
+ $this->random,
+ $this->lockdownManager,
+ $this->logger,
+ $this->dispatcher
+ ])
+ ->onlyMethods([
+ 'setMagicInCookie',
+ ])
+ ->getMock();
+
+ \OC_User::setIncognitoMode(false);
+ }
+
+ public static function isLoggedInData(): array {
+ return [
+ [true],
+ [false],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('isLoggedInData')]
+ public function testIsLoggedIn($isLoggedIn): void {
+ $session = $this->createMock(Memory::class);
+
+ $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])
+ ->onlyMethods([
+ 'getUser'
+ ])
+ ->getMock();
+ $user = new User('sepp', null, $this->createMock(IEventDispatcher::class));
+ $userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($isLoggedIn ? $user : null);
+ $this->assertEquals($isLoggedIn, $userSession->isLoggedIn());
+ }
+
+ public function testSetUser(): void {
+ $session = $this->createMock(Memory::class);
+ $session->expects($this->once())
+ ->method('set')
+ ->with('user_id', 'foo');
+
+ $manager = $this->createMock(Manager::class);
+
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('foo');
+
+ $userSession = new Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher);
+ $userSession->setUser($user);
+ }
+
+ public function testLoginValidPasswordEnabled(): void {
+ $session = $this->createMock(Memory::class);
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->willThrowException(new InvalidTokenException());
+ $session->expects($this->exactly(2))
+ ->method('set')
+ ->with($this->callback(function ($key) {
+ switch ($key) {
+ case 'user_id':
+ case 'loginname':
+ return true;
+ break;
+ default:
+ return false;
+ break;
+ }
+ }, 'foo'));
+
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->any())
+ ->method('isEnabled')
+ ->willReturn(true);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('foo');
+ $user->expects($this->once())
+ ->method('updateLastLoginTimestamp');
+
+ $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])
+ ->onlyMethods([
+ 'prepareUserLogin'
+ ])
+ ->getMock();
+ $userSession->expects($this->once())
+ ->method('prepareUserLogin');
+
+ $this->dispatcher->expects($this->once())
+ ->method('dispatchTyped')
+ ->with(
+ $this->callback(function (PostLoginEvent $e) {
+ return $e->getUser()->getUID() === 'foo'
+ && $e->getPassword() === 'bar'
+ && $e->isTokenLogin() === false;
+ })
+ );
+
+ $userSession->login('foo', 'bar');
+ $this->assertEquals($user, $userSession->getUser());
+ }
+
+
+ public function testLoginValidPasswordDisabled(): void {
+ $this->expectException(LoginException::class);
+
+ $session = $this->createMock(Memory::class);
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->willThrowException(new InvalidTokenException());
+
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->any())
+ ->method('isEnabled')
+ ->willReturn(false);
+ $user->expects($this->never())
+ ->method('updateLastLoginTimestamp');
+
+ $manager->expects($this->once())
+ ->method('checkPasswordNoLogging')
+ ->with('foo', 'bar')
+ ->willReturn($user);
+
+ $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->login('foo', 'bar');
+ }
+
+ public function testLoginInvalidPassword(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->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);
+
+ $user = $this->createMock(IUser::class);
+
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->willThrowException(new InvalidTokenException());
+
+ $user->expects($this->never())
+ ->method('isEnabled');
+ $user->expects($this->never())
+ ->method('updateLastLoginTimestamp');
+
+ $manager->expects($this->once())
+ ->method('checkPasswordNoLogging')
+ ->with('foo', 'bar')
+ ->willReturn(false);
+
+ $this->dispatcher->expects($this->never())
+ ->method('dispatch');
+
+ $userSession->login('foo', 'bar');
+ }
+
+ public function testPasswordlessLoginNoLastCheckUpdate(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ // Keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $token = new PublicKeyToken();
+ $token->setLoginName('foo');
+ $token->setLastCheck(0); // Never
+ $token->setUid('foo');
+ $this->tokenProvider
+ ->method('getPassword')
+ ->with($token)
+ ->willThrowException(new PasswordlessTokenException());
+ $this->tokenProvider
+ ->method('getToken')
+ ->with('app-password')
+ ->willReturn($token);
+ $this->tokenProvider->expects(self::never())
+ ->method('updateToken');
+
+ $userSession->login('foo', 'app-password');
+ }
+
+ public function testLoginLastCheckUpdate(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ // Keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $token = new PublicKeyToken();
+ $token->setLoginName('foo');
+ $token->setLastCheck(0); // Never
+ $token->setUid('foo');
+ $this->tokenProvider
+ ->method('getPassword')
+ ->with($token)
+ ->willReturn('secret');
+ $this->tokenProvider
+ ->method('getToken')
+ ->with('app-password')
+ ->willReturn($token);
+ $this->tokenProvider->expects(self::once())
+ ->method('updateToken');
+
+ $userSession->login('foo', 'app-password');
+ }
+
+ 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);
+
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->willThrowException(new InvalidTokenException());
+
+ $manager->expects($this->once())
+ ->method('checkPasswordNoLogging')
+ ->with('foo', 'bar')
+ ->willReturn(false);
+
+ $userSession->login('foo', 'bar');
+ }
+
+ public function testLogClientInNoTokenPasswordWith2fa(): void {
+ $this->expectException(PasswordLoginForbiddenException::class);
+
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
+ ->getMock();
+
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('doe')
+ ->willThrowException(new InvalidTokenException());
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('token_auth_enforced', false)
+ ->willReturn(true);
+ $request
+ ->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->throttler
+ ->expects($this->once())
+ ->method('sleepDelayOrThrowOnMax')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->any())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(0);
+
+ $userSession->logClientIn('john', 'doe', $request, $this->throttler);
+ }
+
+ public function testLogClientInUnexist(): void {
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
+ ->getMock();
+
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('doe')
+ ->willThrowException(new InvalidTokenException());
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('token_auth_enforced', false)
+ ->willReturn(false);
+ $manager->method('getByEmail')
+ ->with('unexist')
+ ->willReturn([]);
+
+ $this->assertFalse($userSession->logClientIn('unexist', 'doe', $request, $this->throttler));
+ }
+
+ public function testLogClientInWithTokenPassword(): void {
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
+ ->getMock();
+
+ $userSession->expects($this->once())
+ ->method('isTokenPassword')
+ ->willReturn(true);
+ $userSession->expects($this->once())
+ ->method('login')
+ ->with('john', 'I-AM-AN-APP-PASSWORD')
+ ->willReturn(true);
+
+ $session->expects($this->once())
+ ->method('set')
+ ->with('app_password', 'I-AM-AN-APP-PASSWORD');
+ $request
+ ->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->throttler
+ ->expects($this->once())
+ ->method('sleepDelayOrThrowOnMax')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->any())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(0);
+
+ $this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request, $this->throttler));
+ }
+
+
+ public function testLogClientInNoTokenPasswordNo2fa(): void {
+ $this->expectException(PasswordLoginForbiddenException::class);
+
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['login', 'isTwoFactorEnforced'])
+ ->getMock();
+
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('doe')
+ ->willThrowException(new InvalidTokenException());
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('token_auth_enforced', false)
+ ->willReturn(false);
+
+ $userSession->expects($this->once())
+ ->method('isTwoFactorEnforced')
+ ->with('john')
+ ->willReturn(true);
+
+ $request
+ ->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->throttler
+ ->expects($this->once())
+ ->method('sleepDelayOrThrowOnMax')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->any())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(0);
+
+ $userSession->logClientIn('john', 'doe', $request, $this->throttler);
+ }
+
+ public function testTryTokenLoginNoHeaderNoSessionCookie(): void {
+ $request = $this->createMock(IRequest::class);
+ $this->config->expects(self::once())
+ ->method('getSystemValueString')
+ ->with('instanceid')
+ ->willReturn('abc123');
+ $request->method('getHeader')->with('Authorization')->willReturn('');
+ $request->method('getCookie')->with('abc123')->willReturn(null);
+ $this->tokenProvider->expects(self::never())
+ ->method('getToken');
+
+ $loginResult = $this->userSession->tryTokenLogin($request);
+
+ self::assertFalse($loginResult);
+ }
+
+ public function testTryTokenLoginAuthorizationHeaderTokenNotFound(): void {
+ $request = $this->createMock(IRequest::class);
+ $request->method('getHeader')->with('Authorization')->willReturn('Bearer abcde-12345');
+ $this->tokenProvider->expects(self::once())
+ ->method('getToken')
+ ->with('abcde-12345')
+ ->willThrowException(new InvalidTokenException());
+
+ $loginResult = $this->userSession->tryTokenLogin($request);
+
+ self::assertFalse($loginResult);
+ }
+
+ public function testTryTokenLoginSessionIdTokenNotFound(): void {
+ $request = $this->createMock(IRequest::class);
+ $this->config->expects(self::once())
+ ->method('getSystemValueString')
+ ->with('instanceid')
+ ->willReturn('abc123');
+ $request->method('getHeader')->with('Authorization')->willReturn('');
+ $request->method('getCookie')->with('abc123')->willReturn('abcde12345');
+ $this->session->expects(self::once())
+ ->method('getId')
+ ->willReturn('abcde12345');
+ $this->tokenProvider->expects(self::once())
+ ->method('getToken')
+ ->with('abcde12345')
+ ->willThrowException(new InvalidTokenException());
+
+ $loginResult = $this->userSession->tryTokenLogin($request);
+
+ self::assertFalse($loginResult);
+ }
+
+ public function testTryTokenLoginNotAnAppPassword(): void {
+ $request = $this->createMock(IRequest::class);
+ $this->config->expects(self::once())
+ ->method('getSystemValueString')
+ ->with('instanceid')
+ ->willReturn('abc123');
+ $request->method('getHeader')->with('Authorization')->willReturn('');
+ $request->method('getCookie')->with('abc123')->willReturn('abcde12345');
+ $this->session->expects(self::once())
+ ->method('getId')
+ ->willReturn('abcde12345');
+ $dbToken = new PublicKeyToken();
+ $dbToken->setId(42);
+ $dbToken->setUid('johnny');
+ $dbToken->setLoginName('johnny');
+ $dbToken->setLastCheck(0);
+ $dbToken->setType(IToken::TEMPORARY_TOKEN);
+ $dbToken->setRemember(IToken::REMEMBER);
+ $this->tokenProvider->expects(self::any())
+ ->method('getToken')
+ ->with('abcde12345')
+ ->willReturn($dbToken);
+ $this->session->method('set')
+ ->willReturnCallback(function ($key, $value): void {
+ if ($key === 'app_password') {
+ throw new ExpectationFailedException('app_password should not be set in session');
+ }
+ });
+ $user = $this->createMock(IUser::class);
+ $user->method('isEnabled')->willReturn(true);
+ $this->manager->method('get')
+ ->with('johnny')
+ ->willReturn($user);
+
+ $loginResult = $this->userSession->tryTokenLogin($request);
+
+ self::assertTrue($loginResult);
+ }
+
+ public function testRememberLoginValidToken(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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])
+ ->getMock();
+
+ $user = $this->createMock(IUser::class);
+ $token = 'goodToken';
+ $oldSessionId = 'sess321';
+ $sessionId = 'sess123';
+
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $manager->expects($this->once())
+ ->method('get')
+ ->with('foo')
+ ->willReturn($user);
+ $this->config->expects($this->once())
+ ->method('getUserKeys')
+ ->with('foo', 'login_token')
+ ->willReturn([$token]);
+ $this->config->expects($this->once())
+ ->method('deleteUserValue')
+ ->with('foo', 'login_token', $token);
+ $this->random->expects($this->once())
+ ->method('generate')
+ ->with(32)
+ ->willReturn('abcdefg123456');
+ $this->config->expects($this->once())
+ ->method('setUserValue')
+ ->with('foo', 'login_token', 'abcdefg123456', 10000);
+
+ $tokenObject = $this->createMock(IToken::class);
+ $tokenObject->expects($this->once())
+ ->method('getLoginName')
+ ->willReturn('foobar');
+ $tokenObject->method('getId')
+ ->willReturn(42);
+
+ $session->expects($this->once())
+ ->method('getId')
+ ->willReturn($sessionId);
+ $this->tokenProvider->expects($this->once())
+ ->method('renewSessionToken')
+ ->with($oldSessionId, $sessionId)
+ ->willReturn($tokenObject);
+
+ $this->tokenProvider->expects($this->never())
+ ->method('getToken');
+
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('foo');
+ $userSession->expects($this->once())
+ ->method('setMagicInCookie');
+ $user->expects($this->once())
+ ->method('updateLastLoginTimestamp');
+ $setUID = false;
+ $session
+ ->method('set')
+ ->willReturnCallback(function ($k, $v) use (&$setUID): void {
+ if ($k === 'user_id' && $v === 'foo') {
+ $setUID = true;
+ }
+ });
+ $userSession->expects($this->once())
+ ->method('setLoginName')
+ ->willReturn('foobar');
+
+ $granted = $userSession->loginWithCookie('foo', $token, $oldSessionId);
+
+ $this->assertTrue($setUID);
+
+ $this->assertTrue($granted);
+ }
+
+ public function testRememberLoginInvalidSessionToken(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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])
+ ->getMock();
+
+ $user = $this->createMock(IUser::class);
+ $token = 'goodToken';
+ $oldSessionId = 'sess321';
+ $sessionId = 'sess123';
+
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $manager->expects($this->once())
+ ->method('get')
+ ->with('foo')
+ ->willReturn($user);
+ $this->config->expects($this->once())
+ ->method('getUserKeys')
+ ->with('foo', 'login_token')
+ ->willReturn([$token]);
+ $this->config->expects($this->once())
+ ->method('deleteUserValue')
+ ->with('foo', 'login_token', $token);
+ $this->config->expects($this->once())
+ ->method('setUserValue'); // TODO: mock new random value
+
+ $session->expects($this->once())
+ ->method('getId')
+ ->willReturn($sessionId);
+ $this->tokenProvider->expects($this->once())
+ ->method('renewSessionToken')
+ ->with($oldSessionId, $sessionId)
+ ->willThrowException(new InvalidTokenException());
+
+ $user->expects($this->never())
+ ->method('getUID')
+ ->willReturn('foo');
+ $userSession->expects($this->never())
+ ->method('setMagicInCookie');
+ $user->expects($this->never())
+ ->method('updateLastLoginTimestamp');
+ $session->expects($this->never())
+ ->method('set')
+ ->with('user_id', 'foo');
+
+ $granted = $userSession->loginWithCookie('foo', $token, $oldSessionId);
+
+ $this->assertFalse($granted);
+ }
+
+ public function testRememberLoginInvalidToken(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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])
+ ->getMock();
+
+ $user = $this->createMock(IUser::class);
+ $token = 'goodToken';
+ $oldSessionId = 'sess321';
+
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $manager->expects($this->once())
+ ->method('get')
+ ->with('foo')
+ ->willReturn($user);
+ $this->config->expects($this->once())
+ ->method('getUserKeys')
+ ->with('foo', 'login_token')
+ ->willReturn(['anothertoken']);
+ $this->config->expects($this->never())
+ ->method('deleteUserValue')
+ ->with('foo', 'login_token', $token);
+
+ $this->tokenProvider->expects($this->never())
+ ->method('renewSessionToken');
+ $userSession->expects($this->never())
+ ->method('setMagicInCookie');
+ $user->expects($this->never())
+ ->method('updateLastLoginTimestamp');
+ $session->expects($this->never())
+ ->method('set')
+ ->with('user_id', 'foo');
+
+ $granted = $userSession->loginWithCookie('foo', $token, $oldSessionId);
+
+ $this->assertFalse($granted);
+ }
+
+ public function testRememberLoginInvalidUser(): void {
+ $session = $this->createMock(Memory::class);
+ $managerMethods = get_class_methods(Manager::class);
+ //keep following methods intact in order to ensure hooks are working
+ $mockedManagerMethods = array_diff($managerMethods, ['__construct', 'emit', 'listen']);
+ $manager = $this->getMockBuilder(Manager::class)
+ ->onlyMethods($mockedManagerMethods)
+ ->setConstructorArgs([
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ])
+ ->getMock();
+ $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])
+ ->getMock();
+ $token = 'goodToken';
+ $oldSessionId = 'sess321';
+
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $manager->expects($this->once())
+ ->method('get')
+ ->with('foo')
+ ->willReturn(null);
+ $this->config->expects($this->never())
+ ->method('getUserKeys')
+ ->with('foo', 'login_token')
+ ->willReturn(['anothertoken']);
+
+ $this->tokenProvider->expects($this->never())
+ ->method('renewSessionToken');
+ $userSession->expects($this->never())
+ ->method('setMagicInCookie');
+ $session->expects($this->never())
+ ->method('set')
+ ->with('user_id', 'foo');
+
+ $granted = $userSession->loginWithCookie('foo', $token, $oldSessionId);
+
+ $this->assertFalse($granted);
+ }
+
+ public function testActiveUserAfterSetSession(): void {
+ $users = [
+ 'foo' => new User('foo', null, $this->createMock(IEventDispatcher::class)),
+ 'bar' => new User('bar', null, $this->createMock(IEventDispatcher::class))
+ ];
+
+ $manager = $this->getMockBuilder(Manager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $manager->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($uid) use ($users) {
+ return $users[$uid];
+ });
+
+ $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])
+ ->onlyMethods([
+ 'validateSession'
+ ])
+ ->getMock();
+ $userSession->expects($this->any())
+ ->method('validateSession');
+
+ $this->assertEquals($users['foo'], $userSession->getUser());
+
+ $session2 = new Memory();
+ $session2->set('user_id', 'bar');
+ $userSession->setSession($session2);
+ $this->assertEquals($users['bar'], $userSession->getUser());
+ }
+
+ 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);
+
+ $requestId = $this->createMock(IRequestId::class);
+ $config = $this->createMock(IConfig::class);
+ $csrf = $this->getMockBuilder(CsrfTokenManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $request = new Request([
+ 'server' => [
+ 'HTTP_USER_AGENT' => 'Firefox',
+ ]
+ ], $requestId, $config, $csrf);
+
+ $uid = 'user123';
+ $loginName = 'User123';
+ $password = 'passme';
+ $sessionId = 'abcxyz';
+
+ $manager->expects($this->once())
+ ->method('get')
+ ->with($uid)
+ ->willReturn($user);
+ $session->expects($this->once())
+ ->method('getId')
+ ->willReturn($sessionId);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with($password)
+ ->willThrowException(new InvalidTokenException());
+
+ $this->tokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with($sessionId, $uid, $loginName, $password, 'Firefox', IToken::TEMPORARY_TOKEN, IToken::DO_NOT_REMEMBER);
+
+ $this->assertTrue($userSession->createSessionToken($request, $uid, $loginName, $password));
+ }
+
+ 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);
+
+ $requestId = $this->createMock(IRequestId::class);
+ $config = $this->createMock(IConfig::class);
+ $csrf = $this->getMockBuilder(CsrfTokenManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $request = new Request([
+ 'server' => [
+ 'HTTP_USER_AGENT' => 'Firefox',
+ ]
+ ], $requestId, $config, $csrf);
+
+ $uid = 'user123';
+ $loginName = 'User123';
+ $password = 'passme';
+ $sessionId = 'abcxyz';
+
+ $manager->expects($this->once())
+ ->method('get')
+ ->with($uid)
+ ->willReturn($user);
+ $session->expects($this->once())
+ ->method('getId')
+ ->willReturn($sessionId);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with($password)
+ ->willThrowException(new InvalidTokenException());
+
+ $this->tokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with($sessionId, $uid, $loginName, $password, 'Firefox', IToken::TEMPORARY_TOKEN, IToken::REMEMBER);
+
+ $this->assertTrue($userSession->createSessionToken($request, $uid, $loginName, $password, true));
+ }
+
+ public function testCreateSessionTokenWithTokenPassword(): void {
+ $manager = $this->getMockBuilder(Manager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $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);
+
+ $requestId = $this->createMock(IRequestId::class);
+ $config = $this->createMock(IConfig::class);
+ $csrf = $this->getMockBuilder(CsrfTokenManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $request = new Request([
+ 'server' => [
+ 'HTTP_USER_AGENT' => 'Firefox',
+ ]
+ ], $requestId, $config, $csrf);
+
+ $uid = 'user123';
+ $loginName = 'User123';
+ $password = 'iamatoken';
+ $realPassword = 'passme';
+ $sessionId = 'abcxyz';
+
+ $manager->expects($this->once())
+ ->method('get')
+ ->with($uid)
+ ->willReturn($user);
+ $session->expects($this->once())
+ ->method('getId')
+ ->willReturn($sessionId);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with($password)
+ ->willReturn($token);
+ $this->tokenProvider->expects($this->once())
+ ->method('getPassword')
+ ->with($token, $password)
+ ->willReturn($realPassword);
+
+ $this->tokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with($sessionId, $uid, $loginName, $realPassword, 'Firefox', IToken::TEMPORARY_TOKEN, IToken::DO_NOT_REMEMBER);
+
+ $this->assertTrue($userSession->createSessionToken($request, $uid, $loginName, $password));
+ }
+
+ public function testCreateSessionTokenWithNonExistentUser(): void {
+ $manager = $this->getMockBuilder(Manager::class)
+ ->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);
+ $request = $this->createMock(IRequest::class);
+
+ $uid = 'user123';
+ $loginName = 'User123';
+ $password = 'passme';
+
+ $manager->expects($this->once())
+ ->method('get')
+ ->with($uid)
+ ->willReturn(null);
+
+ $this->assertFalse($userSession->createSessionToken($request, $uid, $loginName, $password));
+ }
+
+ public function testCreateRememberMeToken(): void {
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->exactly(2))
+ ->method('getUID')
+ ->willReturn('UserUid');
+ $this->random
+ ->expects($this->once())
+ ->method('generate')
+ ->with(32)
+ ->willReturn('LongRandomToken');
+ $this->config
+ ->expects($this->once())
+ ->method('setUserValue')
+ ->with('UserUid', 'login_token', 'LongRandomToken', 10000);
+ $this->userSession
+ ->expects($this->once())
+ ->method('setMagicInCookie')
+ ->with('UserUid', 'LongRandomToken');
+
+ $this->userSession->createRememberMeToken($user);
+ }
+
+ public function testTryBasicAuthLoginValid(): void {
+ $request = $this->createMock(Request::class);
+ $request->method('__get')
+ ->willReturn([
+ 'PHP_AUTH_USER' => 'username',
+ 'PHP_AUTH_PW' => 'password',
+ ]);
+ $request->method('__isset')
+ ->with('server')
+ ->willReturn(true);
+
+ $davAuthenticatedSet = false;
+ $lastPasswordConfirmSet = false;
+
+ $this->session
+ ->method('set')
+ ->willReturnCallback(function ($k, $v) use (&$davAuthenticatedSet, &$lastPasswordConfirmSet): void {
+ switch ($k) {
+ case Auth::DAV_AUTHENTICATED:
+ $davAuthenticatedSet = $v;
+ return;
+ case 'last-password-confirm':
+ $lastPasswordConfirmSet = 1000;
+ return;
+ default:
+ throw new \Exception();
+ }
+ });
+
+ $userSession = $this->getMockBuilder(Session::class)
+ ->setConstructorArgs([
+ $this->manager,
+ $this->session,
+ $this->timeFactory,
+ $this->tokenProvider,
+ $this->config,
+ $this->random,
+ $this->lockdownManager,
+ $this->logger,
+ $this->dispatcher
+ ])
+ ->onlyMethods([
+ 'logClientIn',
+ 'getUser',
+ ])
+ ->getMock();
+
+ /** @var Session|MockObject */
+ $userSession->expects($this->once())
+ ->method('logClientIn')
+ ->with(
+ $this->equalTo('username'),
+ $this->equalTo('password'),
+ $this->equalTo($request),
+ $this->equalTo($this->throttler)
+ )->willReturn(true);
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('username');
+
+ $userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+
+ $this->assertTrue($userSession->tryBasicAuthLogin($request, $this->throttler));
+
+ $this->assertSame('username', $davAuthenticatedSet);
+ $this->assertSame(1000, $lastPasswordConfirmSet);
+ }
+
+ public function testTryBasicAuthLoginNoLogin(): void {
+ $request = $this->createMock(Request::class);
+ $request->method('__get')
+ ->willReturn([]);
+ $request->method('__isset')
+ ->with('server')
+ ->willReturn(true);
+
+ $this->session->expects($this->never())
+ ->method($this->anything());
+
+ $userSession = $this->getMockBuilder(Session::class)
+ ->setConstructorArgs([
+ $this->manager,
+ $this->session,
+ $this->timeFactory,
+ $this->tokenProvider,
+ $this->config,
+ $this->random,
+ $this->lockdownManager,
+ $this->logger,
+ $this->dispatcher
+ ])
+ ->onlyMethods([
+ 'logClientIn',
+ ])
+ ->getMock();
+
+ /** @var Session|MockObject */
+ $userSession->expects($this->never())
+ ->method('logClientIn');
+
+ $this->assertFalse($userSession->tryBasicAuthLogin($request, $this->throttler));
+ }
+
+ public function testUpdateTokens(): void {
+ $this->tokenProvider->expects($this->once())
+ ->method('updatePasswords')
+ ->with('uid', 'pass');
+
+ $this->userSession->updateTokens('uid', 'pass');
+ }
+
+ public function testLogClientInThrottlerUsername(): void {
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
+ ->getMock();
+
+ $userSession->expects($this->once())
+ ->method('isTokenPassword')
+ ->willReturn(true);
+ $userSession->expects($this->once())
+ ->method('login')
+ ->with('john', 'I-AM-AN-PASSWORD')
+ ->willReturn(false);
+
+ $session->expects($this->never())
+ ->method('set');
+ $request
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->throttler
+ ->expects($this->exactly(2))
+ ->method('sleepDelayOrThrowOnMax')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->any())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(0);
+
+ $this->throttler
+ ->expects($this->once())
+ ->method('registerAttempt')
+ ->with('login', '192.168.0.1', ['user' => 'john']);
+ $this->dispatcher
+ ->expects($this->once())
+ ->method('dispatchTyped')
+ ->with(new LoginFailed('john', 'I-AM-AN-PASSWORD'));
+
+ $this->assertFalse($userSession->logClientIn('john', 'I-AM-AN-PASSWORD', $request, $this->throttler));
+ }
+
+ public function testLogClientInThrottlerEmail(): void {
+ $manager = $this->createMock(Manager::class);
+ $session = $this->createMock(ISession::class);
+ $request = $this->createMock(IRequest::class);
+
+ /** @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])
+ ->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
+ ->getMock();
+
+ $userSession->expects($this->once())
+ ->method('isTokenPassword')
+ ->willReturn(false);
+ $userSession->expects($this->once())
+ ->method('login')
+ ->with('john@foo.bar', 'I-AM-AN-PASSWORD')
+ ->willReturn(false);
+ $manager
+ ->method('getByEmail')
+ ->with('john@foo.bar')
+ ->willReturn([]);
+
+ $session->expects($this->never())
+ ->method('set');
+ $request
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->throttler
+ ->expects($this->exactly(2))
+ ->method('sleepDelayOrThrowOnMax')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->any())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(0);
+
+ $this->throttler
+ ->expects($this->once())
+ ->method('registerAttempt')
+ ->with('login', '192.168.0.1', ['user' => 'john@foo.bar']);
+ $this->dispatcher
+ ->expects($this->once())
+ ->method('dispatchTyped')
+ ->with(new LoginFailed('john@foo.bar', 'I-AM-AN-PASSWORD'));
+
+ $this->assertFalse($userSession->logClientIn('john@foo.bar', 'I-AM-AN-PASSWORD', $request, $this->throttler));
+ }
+}