aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/User
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/User')
-rw-r--r--tests/lib/User/AvailabilityCoordinatorTest.php220
-rw-r--r--tests/lib/User/AvatarUserDummy.php15
-rw-r--r--tests/lib/User/Backend.php111
-rw-r--r--tests/lib/User/DatabaseTest.php157
-rw-r--r--tests/lib/User/Dummy.php16
-rw-r--r--tests/lib/User/ManagerTest.php807
-rw-r--r--tests/lib/User/SessionTest.php1324
-rw-r--r--tests/lib/User/UserTest.php1032
8 files changed, 3682 insertions, 0 deletions
diff --git a/tests/lib/User/AvailabilityCoordinatorTest.php b/tests/lib/User/AvailabilityCoordinatorTest.php
new file mode 100644
index 00000000000..09c1528912b
--- /dev/null
+++ b/tests/lib/User/AvailabilityCoordinatorTest.php
@@ -0,0 +1,220 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\User;
+
+use OC\User\AvailabilityCoordinator;
+use OC\User\OutOfOfficeData;
+use OCA\DAV\CalDAV\TimezoneService;
+use OCA\DAV\Db\Absence;
+use OCA\DAV\Service\AbsenceService;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IUser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class AvailabilityCoordinatorTest extends TestCase {
+ private AvailabilityCoordinator $availabilityCoordinator;
+ private ICacheFactory $cacheFactory;
+ private ICache $cache;
+ private IConfig|MockObject $config;
+ private AbsenceService $absenceService;
+ private LoggerInterface $logger;
+ private MockObject|TimezoneService $timezoneService;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->cache = $this->createMock(ICache::class);
+ $this->absenceService = $this->createMock(AbsenceService::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->timezoneService = $this->createMock(TimezoneService::class);
+
+ $this->cacheFactory->expects(self::once())
+ ->method('createLocal')
+ ->willReturn($this->cache);
+
+ $this->availabilityCoordinator = new AvailabilityCoordinator(
+ $this->cacheFactory,
+ $this->config,
+ $this->absenceService,
+ $this->logger,
+ $this->timezoneService,
+ );
+ }
+
+ public function testIsEnabled(): void {
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('dav', 'hide_absence_settings', 'no')
+ ->willReturn('no');
+
+ $isEnabled = $this->availabilityCoordinator->isEnabled();
+
+ self::assertTrue($isEnabled);
+ }
+
+ public function testGetOutOfOfficeDataInEffect(): void {
+ $absence = new Absence();
+ $absence->setId(420);
+ $absence->setUserId('user');
+ $absence->setFirstDay('2023-10-01');
+ $absence->setLastDay('2023-10-08');
+ $absence->setStatus('Vacation');
+ $absence->setMessage('On vacation');
+ $absence->setReplacementUserId('batman');
+ $absence->setReplacementUserDisplayName('Bruce Wayne');
+ $this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin');
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')
+ ->willReturn('user');
+
+ $this->cache->expects(self::exactly(2))
+ ->method('get')
+ ->willReturnOnConsecutiveCalls(null, null);
+ $this->absenceService->expects(self::once())
+ ->method('getAbsence')
+ ->with($user->getUID())
+ ->willReturn($absence);
+
+ $calls = [
+ [$user->getUID() . '_timezone', 'Europe/Berlin', 3600],
+ [$user->getUID(), '{"id":"420","startDate":1696111200,"endDate":1696802340,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}', 300],
+ ];
+ $this->cache->expects(self::exactly(2))
+ ->method('set')
+ ->willReturnCallback(static function () use (&$calls): void {
+ $expected = array_shift($calls);
+ self::assertEquals($expected, func_get_args());
+ });
+
+ $expected = new OutOfOfficeData(
+ '420',
+ $user,
+ 1696111200,
+ 1696802340,
+ 'Vacation',
+ 'On vacation',
+ 'batman',
+ 'Bruce Wayne',
+ );
+ $actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testGetOutOfOfficeDataCachedAll(): void {
+ $absence = new Absence();
+ $absence->setId(420);
+ $absence->setUserId('user');
+ $absence->setFirstDay('2023-10-01');
+ $absence->setLastDay('2023-10-08');
+ $absence->setStatus('Vacation');
+ $absence->setMessage('On vacation');
+ $absence->setReplacementUserId('batman');
+ $absence->setReplacementUserDisplayName('Bruce Wayne');
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')
+ ->willReturn('user');
+
+ $this->cache->expects(self::exactly(2))
+ ->method('get')
+ ->willReturnOnConsecutiveCalls('UTC', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}');
+ $this->absenceService->expects(self::never())
+ ->method('getAbsence');
+ $this->cache->expects(self::exactly(1))
+ ->method('set');
+
+ $expected = new OutOfOfficeData(
+ '420',
+ $user,
+ 1696118400,
+ 1696809540,
+ 'Vacation',
+ 'On vacation',
+ 'batman',
+ 'Bruce Wayne'
+ );
+ $actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testGetOutOfOfficeDataNoData(): void {
+ $absence = new Absence();
+ $absence->setId(420);
+ $absence->setUserId('user');
+ $absence->setFirstDay('2023-10-01');
+ $absence->setLastDay('2023-10-08');
+ $absence->setStatus('Vacation');
+ $absence->setMessage('On vacation');
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')
+ ->willReturn('user');
+
+ $this->cache->expects(self::exactly(2))
+ ->method('get')
+ ->willReturnOnConsecutiveCalls('UTC', null);
+ $this->absenceService->expects(self::once())
+ ->method('getAbsence')
+ ->willReturn(null);
+ $this->cache->expects(self::never())
+ ->method('set');
+
+ $actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
+ self::assertNull($actual);
+ }
+
+ public function testGetOutOfOfficeDataWithInvalidCachedData(): void {
+ $absence = new Absence();
+ $absence->setId(420);
+ $absence->setUserId('user');
+ $absence->setFirstDay('2023-10-01');
+ $absence->setLastDay('2023-10-08');
+ $absence->setStatus('Vacation');
+ $absence->setMessage('On vacation');
+ $absence->setReplacementUserId('batman');
+ $absence->setReplacementUserDisplayName('Bruce Wayne');
+ $this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin');
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')
+ ->willReturn('user');
+
+ $this->cache->expects(self::exactly(2))
+ ->method('get')
+ ->willReturnOnConsecutiveCalls('UTC', '{"id":"420",}');
+ $this->absenceService->expects(self::once())
+ ->method('getAbsence')
+ ->with('user')
+ ->willReturn($absence);
+ $this->cache->expects(self::once())
+ ->method('set')
+ ->with('user', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}', 300);
+
+ $expected = new OutOfOfficeData(
+ '420',
+ $user,
+ 1696118400,
+ 1696809540,
+ 'Vacation',
+ 'On vacation',
+ 'batman',
+ 'Bruce Wayne'
+ );
+ $actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
+ self::assertEquals($expected, $actual);
+ }
+}
diff --git a/tests/lib/User/AvatarUserDummy.php b/tests/lib/User/AvatarUserDummy.php
new file mode 100644
index 00000000000..001dabd24c6
--- /dev/null
+++ b/tests/lib/User/AvatarUserDummy.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2020-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\User;
+
+class AvatarUserDummy extends \Test\Util\User\Dummy {
+ public function canChangeAvatar($uid) {
+ return true;
+ }
+}
diff --git a/tests/lib/User/Backend.php b/tests/lib/User/Backend.php
new file mode 100644
index 00000000000..dc5b245fa06
--- /dev/null
+++ b/tests/lib/User/Backend.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\User;
+
+/**
+ * Abstract class to provide the basis of backend-specific unit test classes.
+ *
+ * All subclasses MUST assign a backend property in setUp() which implements
+ * user operations (add, remove, etc.). Test methods in this class will then be
+ * run on each separate subclass and backend therein.
+ *
+ * For an example see /tests/lib/user/dummy.php
+ */
+
+abstract class Backend extends \Test\TestCase {
+ /**
+ * @var \OC\User\Backend $backend
+ */
+ protected $backend;
+
+ /**
+ * get a new unique user name
+ * test cases can override this in order to clean up created user
+ * @return string
+ */
+ public function getUser() {
+ return $this->getUniqueID('test_');
+ }
+
+ public function testAddRemove(): void {
+ //get the number of groups we start with, in case there are exising groups
+ $startCount = count($this->backend->getUsers());
+
+ $name1 = $this->getUser();
+ $name2 = $this->getUser();
+ $this->backend->createUser($name1, '');
+ $count = count($this->backend->getUsers()) - $startCount;
+ $this->assertEquals(1, $count);
+ $this->assertTrue((array_search($name1, $this->backend->getUsers()) !== false));
+ $this->assertFalse((array_search($name2, $this->backend->getUsers()) !== false));
+ $this->backend->createUser($name2, '');
+ $count = count($this->backend->getUsers()) - $startCount;
+ $this->assertEquals(2, $count);
+ $this->assertTrue((array_search($name1, $this->backend->getUsers()) !== false));
+ $this->assertTrue((array_search($name2, $this->backend->getUsers()) !== false));
+
+ $this->backend->deleteUser($name2);
+ $count = count($this->backend->getUsers()) - $startCount;
+ $this->assertEquals(1, $count);
+ $this->assertTrue((array_search($name1, $this->backend->getUsers()) !== false));
+ $this->assertFalse((array_search($name2, $this->backend->getUsers()) !== false));
+ }
+
+ public function testLogin(): void {
+ $name1 = $this->getUser();
+ $name2 = $this->getUser();
+
+ $this->assertFalse($this->backend->userExists($name1));
+ $this->assertFalse($this->backend->userExists($name2));
+
+ $this->backend->createUser($name1, 'pass1');
+ $this->backend->createUser($name2, 'pass2');
+
+ $this->assertTrue($this->backend->userExists($name1));
+ $this->assertTrue($this->backend->userExists($name2));
+
+ $this->assertSame($name1, $this->backend->checkPassword($name1, 'pass1'));
+ $this->assertSame($name2, $this->backend->checkPassword($name2, 'pass2'));
+
+ $this->assertFalse($this->backend->checkPassword($name1, 'pass2'));
+ $this->assertFalse($this->backend->checkPassword($name2, 'pass1'));
+
+ $this->assertFalse($this->backend->checkPassword($name1, 'dummy'));
+ $this->assertFalse($this->backend->checkPassword($name2, 'foobar'));
+
+ $this->backend->setPassword($name1, 'newpass1');
+ $this->assertFalse($this->backend->checkPassword($name1, 'pass1'));
+ $this->assertSame($name1, $this->backend->checkPassword($name1, 'newpass1'));
+ $this->assertFalse($this->backend->checkPassword($name2, 'newpass1'));
+ }
+
+ public function testSearch(): void {
+ $name1 = 'foobarbaz';
+ $name2 = 'bazbarfoo';
+ $name3 = 'notme';
+ $name4 = 'under_score';
+
+ $this->backend->createUser($name1, 'pass1');
+ $this->backend->createUser($name2, 'pass2');
+ $this->backend->createUser($name3, 'pass3');
+ $this->backend->createUser($name4, 'pass4');
+
+ $result = $this->backend->getUsers('bar');
+ $this->assertCount(2, $result);
+
+ $result = $this->backend->getDisplayNames('bar');
+ $this->assertCount(2, $result);
+
+ $result = $this->backend->getUsers('under_');
+ $this->assertCount(1, $result);
+
+ $result = $this->backend->getUsers('not_');
+ $this->assertCount(0, $result);
+ }
+}
diff --git a/tests/lib/User/DatabaseTest.php b/tests/lib/User/DatabaseTest.php
new file mode 100644
index 00000000000..33101173c0a
--- /dev/null
+++ b/tests/lib/User/DatabaseTest.php
@@ -0,0 +1,157 @@
+<?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\User\Database;
+use OC\User\User;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\HintException;
+use OCP\Security\Events\ValidatePasswordPolicyEvent;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * Class DatabaseTest
+ *
+ * @group DB
+ */
+class DatabaseTest extends Backend {
+ /** @var array */
+ private $users;
+ /** @var IEventDispatcher|MockObject */
+ private $eventDispatcher;
+
+ /** @var \OC\User\Database */
+ protected $backend;
+
+ public function getUser() {
+ $user = parent::getUser();
+ $this->users[] = $user;
+ return $user;
+ }
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+
+ $this->backend = new Database($this->eventDispatcher);
+ }
+
+ protected function tearDown(): void {
+ if (!isset($this->users)) {
+ return;
+ }
+ foreach ($this->users as $user) {
+ $this->backend->deleteUser($user);
+ }
+ parent::tearDown();
+ }
+
+ public function testVerifyPasswordEvent(): void {
+ $user = $this->getUser();
+ $this->backend->createUser($user, 'pass1');
+
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')
+ ->willReturnCallback(
+ function (Event $event): void {
+ $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
+ /** @var ValidatePasswordPolicyEvent $event */
+ $this->assertSame('newpass', $event->getPassword());
+ }
+ );
+
+ $this->backend->setPassword($user, 'newpass');
+ $this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
+ }
+
+
+ public function testVerifyPasswordEventFail(): void {
+ $this->expectException(HintException::class);
+ $this->expectExceptionMessage('password change failed');
+
+ $user = $this->getUser();
+ $this->backend->createUser($user, 'pass1');
+
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')
+ ->willReturnCallback(
+ function (Event $event): void {
+ $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
+ /** @var ValidatePasswordPolicyEvent $event */
+ $this->assertSame('newpass', $event->getPassword());
+ throw new HintException('password change failed', 'password change failed');
+ }
+ );
+
+ $this->backend->setPassword($user, 'newpass');
+ $this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
+ }
+
+ public function testCreateUserInvalidatesCache(): void {
+ $user1 = $this->getUniqueID('test_');
+ $this->assertFalse($this->backend->userExists($user1));
+ $this->backend->createUser($user1, 'pw');
+ $this->assertTrue($this->backend->userExists($user1));
+ }
+
+ public function testDeleteUserInvalidatesCache(): void {
+ $user1 = $this->getUniqueID('test_');
+ $this->backend->createUser($user1, 'pw');
+ $this->assertTrue($this->backend->userExists($user1));
+ $this->backend->deleteUser($user1);
+ $this->assertFalse($this->backend->userExists($user1));
+ $this->backend->createUser($user1, 'pw2');
+ $this->assertTrue($this->backend->userExists($user1));
+ }
+
+ public function testSearch(): void {
+ parent::testSearch();
+
+ $user1 = $this->getUser();
+ $this->backend->createUser($user1, 'pass1');
+
+ $user2 = $this->getUser();
+ $this->backend->createUser($user2, 'pass1');
+
+ $user1Obj = new User($user1, $this->backend, $this->createMock(IEventDispatcher::class));
+ $user2Obj = new User($user2, $this->backend, $this->createMock(IEventDispatcher::class));
+ $emailAddr1 = "$user1@nextcloud.com";
+ $emailAddr2 = "$user2@nextcloud.com";
+
+ $user1Obj->setDisplayName('User 1 Display');
+
+ $result = $this->backend->getDisplayNames('display');
+ $this->assertCount(1, $result);
+
+ $result = $this->backend->getDisplayNames(strtoupper($user1));
+ $this->assertCount(1, $result);
+
+ $user1Obj->setEMailAddress($emailAddr1);
+ $user2Obj->setEMailAddress($emailAddr2);
+
+ $result = $this->backend->getUsers('@nextcloud.com');
+ $this->assertCount(2, $result);
+
+ $result = $this->backend->getDisplayNames('@nextcloud.com');
+ $this->assertCount(2, $result);
+
+ $result = $this->backend->getDisplayNames('@nextcloud.COM');
+ $this->assertCount(2, $result);
+ }
+
+ public function testUserCount(): void {
+ $base = $this->backend->countUsers() ?: 0;
+ $users = $this->backend->getUsers();
+ self::assertEquals($base, count($users));
+
+ $user = $this->getUser();
+ $this->backend->createUser($user, $user);
+ self::assertEquals($base + 1, $this->backend->countUsers());
+ }
+}
diff --git a/tests/lib/User/Dummy.php b/tests/lib/User/Dummy.php
new file mode 100644
index 00000000000..ec5be8ec60a
--- /dev/null
+++ b/tests/lib/User/Dummy.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\User;
+
+class Dummy extends Backend {
+ protected function setUp(): void {
+ parent::setUp();
+ $this->backend = new \Test\Util\User\Dummy();
+ }
+}
diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php
new file mode 100644
index 00000000000..d5872787d0a
--- /dev/null
+++ b/tests/lib/User/ManagerTest.php
@@ -0,0 +1,807 @@
+<?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\AllConfig;
+use OC\USER\BACKEND;
+use OC\User\Database;
+use OC\User\Manager;
+use OC\User\User;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class ManagerTest
+ *
+ * @group DB
+ *
+ * @package Test\User
+ */
+class ManagerTest extends TestCase {
+ /** @var IConfig */
+ private $config;
+ /** @var IEventDispatcher */
+ private $eventDispatcher;
+ /** @var ICacheFactory */
+ private $cacheFactory;
+ /** @var ICache */
+ private $cache;
+ /** @var LoggerInterface */
+ private $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->cache = $this->createMock(ICache::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->cacheFactory->method('createDistributed')
+ ->willReturn($this->cache);
+ }
+
+ public function testGetBackends(): void {
+ $userDummyBackend = $this->createMock(\Test\Util\User\Dummy::class);
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($userDummyBackend);
+ $this->assertEquals([$userDummyBackend], $manager->getBackends());
+ $dummyDatabaseBackend = $this->createMock(Database::class);
+ $manager->registerBackend($dummyDatabaseBackend);
+ $this->assertEquals([$userDummyBackend, $dummyDatabaseBackend], $manager->getBackends());
+ }
+
+
+ public function testUserExistsSingleBackendExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertTrue($manager->userExists('foo'));
+ }
+
+ public function testUserExistsTooLong(): void {
+ /** @var \Test\Util\User\Dummy|MockObject $backend */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->never())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertFalse($manager->userExists('foo' . str_repeat('a', 62)));
+ }
+
+ public function testUserExistsSingleBackendNotExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertFalse($manager->userExists('foo'));
+ }
+
+ public function testUserExistsNoBackends(): void {
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+
+ $this->assertFalse($manager->userExists('foo'));
+ }
+
+ public function testUserExistsTwoBackendsSecondExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend1
+ */
+ $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend1->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend2
+ */
+ $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend2->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend1);
+ $manager->registerBackend($backend2);
+
+ $this->assertTrue($manager->userExists('foo'));
+ }
+
+ public function testUserExistsTwoBackendsFirstExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend1
+ */
+ $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend1->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend2
+ */
+ $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend2->expects($this->never())
+ ->method('userExists');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend1);
+ $manager->registerBackend($backend2);
+
+ $this->assertTrue($manager->userExists('foo'));
+ }
+
+ public function testCheckPassword(): void {
+ /**
+ * @var \OC\User\Backend | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('checkPassword')
+ ->with($this->equalTo('foo'), $this->equalTo('bar'))
+ ->willReturn(true);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === BACKEND::CHECK_PASSWORD) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $user = $manager->checkPassword('foo', 'bar');
+ $this->assertTrue($user instanceof User);
+ }
+
+ public function testCheckPasswordNotSupported(): void {
+ /**
+ * @var \OC\User\Backend | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->never())
+ ->method('checkPassword');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertFalse($manager->checkPassword('foo', 'bar'));
+ }
+
+ public function testGetOneBackendExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+ $backend->expects($this->never())
+ ->method('loginName2UserName');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertEquals('foo', $manager->get('foo')->getUID());
+ }
+
+ public function testGetOneBackendNotExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertEquals(null, $manager->get('foo'));
+ }
+
+ public function testGetTooLong(): void {
+ /** @var \Test\Util\User\Dummy|MockObject $backend */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->never())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertEquals(null, $manager->get('foo' . str_repeat('a', 62)));
+ }
+
+ public function testGetOneBackendDoNotTranslateLoginNames(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('bLeNdEr'))
+ ->willReturn(true);
+ $backend->expects($this->never())
+ ->method('loginName2UserName');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertEquals('bLeNdEr', $manager->get('bLeNdEr')->getUID());
+ }
+
+ public function testSearchOneBackend(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('getUsers')
+ ->with($this->equalTo('fo'))
+ ->willReturn(['foo', 'afoo', 'Afoo1', 'Bfoo']);
+ $backend->expects($this->never())
+ ->method('loginName2UserName');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $result = $manager->search('fo');
+ $this->assertEquals(4, count($result));
+ $this->assertEquals('afoo', array_shift($result)->getUID());
+ $this->assertEquals('Afoo1', array_shift($result)->getUID());
+ $this->assertEquals('Bfoo', array_shift($result)->getUID());
+ $this->assertEquals('foo', array_shift($result)->getUID());
+ }
+
+ public function testSearchTwoBackendLimitOffset(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend1
+ */
+ $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend1->expects($this->once())
+ ->method('getUsers')
+ ->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
+ ->willReturn(['foo1', 'foo2']);
+ $backend1->expects($this->never())
+ ->method('loginName2UserName');
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend2
+ */
+ $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend2->expects($this->once())
+ ->method('getUsers')
+ ->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
+ ->willReturn(['foo3']);
+ $backend2->expects($this->never())
+ ->method('loginName2UserName');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend1);
+ $manager->registerBackend($backend2);
+
+ $result = $manager->search('fo', 3, 1);
+ $this->assertEquals(3, count($result));
+ $this->assertEquals('foo1', array_shift($result)->getUID());
+ $this->assertEquals('foo2', array_shift($result)->getUID());
+ $this->assertEquals('foo3', array_shift($result)->getUID());
+ }
+
+ public static function dataCreateUserInvalid(): array {
+ return [
+ ['te?st', 'foo', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\tst", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\nst", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\rst", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\0st", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\x0Bst", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\xe2st", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\x80st", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ["te\x8bst", '', 'Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
+ ['', 'foo', 'A valid username must be provided'],
+ [' ', 'foo', 'A valid username must be provided'],
+ [' test', 'foo', 'Username contains whitespace at the beginning or at the end'],
+ ['test ', 'foo', 'Username contains whitespace at the beginning or at the end'],
+ ['.', 'foo', 'Username must not consist of dots only'],
+ ['..', 'foo', 'Username must not consist of dots only'],
+ ['.test', '', 'A valid password must be provided'],
+ ['test', '', 'A valid password must be provided'],
+ ['test' . str_repeat('a', 61), '', 'Login is too long'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataCreateUserInvalid')]
+ public function testCreateUserInvalid($uid, $password, $exception): void {
+ /** @var \Test\Util\User\Dummy|\PHPUnit\Framework\MockObject\MockObject $backend */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('implementsActions')
+ ->with(\OC\User\Backend::CREATE_USER)
+ ->willReturn(true);
+
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->expectException(\InvalidArgumentException::class, $exception);
+ $manager->createUser($uid, $password);
+ }
+
+ public function testCreateUserSingleBackendNotExists(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(true);
+
+ $backend->expects($this->once())
+ ->method('createUser')
+ ->with($this->equalTo('foo'), $this->equalTo('bar'));
+
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+ $backend->expects($this->never())
+ ->method('loginName2UserName');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $user = $manager->createUser('foo', 'bar');
+ $this->assertEquals('foo', $user->getUID());
+ }
+
+
+ public function testCreateUserSingleBackendExists(): void {
+ $this->expectException(\Exception::class);
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(true);
+
+ $backend->expects($this->never())
+ ->method('createUser');
+
+ $backend->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $manager->createUser('foo', 'bar');
+ }
+
+ public function testCreateUserSingleBackendNotSupported(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $backend->expects($this->never())
+ ->method('createUser');
+
+ $backend->expects($this->never())
+ ->method('userExists');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $this->assertFalse($manager->createUser('foo', 'bar'));
+ }
+
+ public function testCreateUserNoBackends(): void {
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+
+ $this->assertFalse($manager->createUser('foo', 'bar'));
+ }
+
+
+ public function testCreateUserFromBackendWithBackendError(): void {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Could not create account');
+
+ /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject $config */
+ $config = $this->createMock(IConfig::class);
+ /** @var \Test\Util\User\Dummy|\PHPUnit\Framework\MockObject\MockObject $backend */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend
+ ->expects($this->once())
+ ->method('createUser')
+ ->with('MyUid', 'MyPassword')
+ ->willReturn(false);
+
+ $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->createUserFromBackend('MyUid', 'MyPassword', $backend);
+ }
+
+
+ public function testCreateUserTwoBackendExists(): void {
+ $this->expectException(\Exception::class);
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend1
+ */
+ $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend1->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(true);
+
+ $backend1->expects($this->never())
+ ->method('createUser');
+
+ $backend1->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend2
+ */
+ $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend2->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(true);
+
+ $backend2->expects($this->never())
+ ->method('createUser');
+
+ $backend2->expects($this->once())
+ ->method('userExists')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend1);
+ $manager->registerBackend($backend2);
+
+ $manager->createUser('foo', 'bar');
+ }
+
+ public function testCountUsersNoBackend(): void {
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+
+ $result = $manager->countUsers();
+ $this->assertTrue(is_array($result));
+ $this->assertTrue(empty($result));
+ }
+
+ public function testCountUsersOneBackend(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('countUsers')
+ ->willReturn(7);
+
+ $backend->expects($this->once())
+ ->method('implementsActions')
+ ->with(BACKEND::COUNT_USERS)
+ ->willReturn(true);
+
+ $backend->expects($this->once())
+ ->method('getBackendName')
+ ->willReturn('Mock_Test_Util_User_Dummy');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $result = $manager->countUsers();
+ $keys = array_keys($result);
+ $this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
+
+ $users = array_shift($result);
+ $this->assertEquals(7, $users);
+ }
+
+ public function testCountUsersTwoBackends(): void {
+ /**
+ * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend
+ */
+ $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend1->expects($this->once())
+ ->method('countUsers')
+ ->willReturn(7);
+
+ $backend1->expects($this->once())
+ ->method('implementsActions')
+ ->with(BACKEND::COUNT_USERS)
+ ->willReturn(true);
+ $backend1->expects($this->once())
+ ->method('getBackendName')
+ ->willReturn('Mock_Test_Util_User_Dummy');
+
+ $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend2->expects($this->once())
+ ->method('countUsers')
+ ->willReturn(16);
+
+ $backend2->expects($this->once())
+ ->method('implementsActions')
+ ->with(BACKEND::COUNT_USERS)
+ ->willReturn(true);
+ $backend2->expects($this->once())
+ ->method('getBackendName')
+ ->willReturn('Mock_Test_Util_User_Dummy');
+
+ $manager = new \OC\User\Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend1);
+ $manager->registerBackend($backend2);
+
+ $result = $manager->countUsers();
+ //because the backends have the same class name, only one value expected
+ $this->assertEquals(1, count($result));
+ $keys = array_keys($result);
+ $this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
+
+ $users = array_shift($result);
+ //users from backends shall be summed up
+ $this->assertEquals(7 + 16, $users);
+ }
+
+ public function testCountUsersOnlyDisabled(): void {
+ $manager = Server::get(IUserManager::class);
+ // count other users in the db before adding our own
+ $countBefore = $manager->countDisabledUsers();
+
+ //Add test users
+ $user1 = $manager->createUser('testdisabledcount1', 'testdisabledcount1');
+
+ $user2 = $manager->createUser('testdisabledcount2', 'testdisabledcount2');
+ $user2->setEnabled(false);
+
+ $user3 = $manager->createUser('testdisabledcount3', 'testdisabledcount3');
+
+ $user4 = $manager->createUser('testdisabledcount4', 'testdisabledcount4');
+ $user4->setEnabled(false);
+
+ $this->assertEquals($countBefore + 2, $manager->countDisabledUsers());
+
+ //cleanup
+ $user1->delete();
+ $user2->delete();
+ $user3->delete();
+ $user4->delete();
+ }
+
+ public function testCountUsersOnlySeen(): void {
+ $manager = Server::get(IUserManager::class);
+ // count other users in the db before adding our own
+ $countBefore = $manager->countSeenUsers();
+
+ //Add test users
+ $user1 = $manager->createUser('testseencount1', 'testseencount1');
+ $user1->updateLastLoginTimestamp();
+
+ $user2 = $manager->createUser('testseencount2', 'testseencount2');
+ $user2->updateLastLoginTimestamp();
+
+ $user3 = $manager->createUser('testseencount3', 'testseencount3');
+
+ $user4 = $manager->createUser('testseencount4', 'testseencount4');
+ $user4->updateLastLoginTimestamp();
+
+ $this->assertEquals($countBefore + 3, $manager->countSeenUsers());
+
+ //cleanup
+ $user1->delete();
+ $user2->delete();
+ $user3->delete();
+ $user4->delete();
+ }
+
+ public function testCallForSeenUsers(): void {
+ $manager = Server::get(IUserManager::class);
+ // count other users in the db before adding our own
+ $count = 0;
+ $function = function (IUser $user) use (&$count): void {
+ $count++;
+ };
+ $manager->callForAllUsers($function, '', true);
+ $countBefore = $count;
+
+ //Add test users
+ $user1 = $manager->createUser('testseen1', 'testseen10');
+ $user1->updateLastLoginTimestamp();
+
+ $user2 = $manager->createUser('testseen2', 'testseen20');
+ $user2->updateLastLoginTimestamp();
+
+ $user3 = $manager->createUser('testseen3', 'testseen30');
+
+ $user4 = $manager->createUser('testseen4', 'testseen40');
+ $user4->updateLastLoginTimestamp();
+
+ $count = 0;
+ $manager->callForAllUsers($function, '', true);
+
+ $this->assertEquals($countBefore + 3, $count);
+
+ //cleanup
+ $user1->delete();
+ $user2->delete();
+ $user3->delete();
+ $user4->delete();
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testRecentlyActive(): void {
+ $config = Server::get(IConfig::class);
+ $manager = Server::get(IUserManager::class);
+
+ // Create some users
+ $now = (string)time();
+ $user1 = $manager->createUser('test_active_1', 'test_active_1');
+ $config->setUserValue('test_active_1', 'login', 'lastLogin', $now);
+ $user1->setDisplayName('test active 1');
+ $user1->setSystemEMailAddress('roger@active.com');
+
+ $user2 = $manager->createUser('TEST_ACTIVE_2_FRED', 'TEST_ACTIVE_2');
+ $config->setUserValue('TEST_ACTIVE_2_FRED', 'login', 'lastLogin', $now);
+ $user2->setDisplayName('TEST ACTIVE 2 UPPER');
+ $user2->setSystemEMailAddress('Fred@Active.Com');
+
+ $user3 = $manager->createUser('test_active_3', 'test_active_3');
+ $config->setUserValue('test_active_3', 'login', 'lastLogin', $now + 1);
+ $user3->setDisplayName('test active 3');
+
+ $user4 = $manager->createUser('test_active_4', 'test_active_4');
+ $config->setUserValue('test_active_4', 'login', 'lastLogin', $now);
+ $user4->setDisplayName('Test Active 4');
+
+ $user5 = $manager->createUser('test_inactive_1', 'test_inactive_1');
+ $user5->setDisplayName('Test Inactive 1');
+ $user2->setSystemEMailAddress('jeanne@Active.Com');
+
+ // Search recently active
+ // - No search, case-insensitive order
+ $users = $manager->getLastLoggedInUsers(4);
+ $this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
+ // - Search, case-insensitive order
+ $users = $manager->getLastLoggedInUsers(search: 'act');
+ $this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
+ // - No search with offset
+ $users = $manager->getLastLoggedInUsers(2, 2);
+ $this->assertEquals(['TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
+ // - Case insensitive search (email)
+ $users = $manager->getLastLoggedInUsers(search: 'active.com');
+ $this->assertEquals(['test_active_1', 'TEST_ACTIVE_2_FRED'], $users);
+ // - Case insensitive search (display name)
+ $users = $manager->getLastLoggedInUsers(search: 'upper');
+ $this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
+ // - Case insensitive search (uid)
+ $users = $manager->getLastLoggedInUsers(search: 'fred');
+ $this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
+
+ // Delete users and config keys
+ $user1->delete();
+ $user2->delete();
+ $user3->delete();
+ $user4->delete();
+ $user5->delete();
+ }
+
+ public function testDeleteUser(): void {
+ $config = $this->getMockBuilder(AllConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $config
+ ->expects($this->any())
+ ->method('getUserValue')
+ ->willReturnArgument(3);
+ $config
+ ->expects($this->any())
+ ->method('getAppValue')
+ ->willReturnArgument(2);
+
+ $manager = new \OC\User\Manager($config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $backend = new \Test\Util\User\Dummy();
+
+ $manager->registerBackend($backend);
+ $backend->createUser('foo', 'bar');
+ $this->assertTrue($manager->userExists('foo'));
+ $manager->get('foo')->delete();
+ $this->assertFalse($manager->userExists('foo'));
+ }
+
+ public function testGetByEmail(): void {
+ $config = $this->getMockBuilder(AllConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $config
+ ->expects($this->once())
+ ->method('getUsersForUserValueCaseInsensitive')
+ ->with('settings', 'email', 'test@example.com')
+ ->willReturn(['uid1', 'uid99', 'uid2']);
+
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->exactly(3))
+ ->method('userExists')
+ ->willReturnMap([
+ ['uid1', true],
+ ['uid99', false],
+ ['uid2', true]
+ ]);
+
+ $manager = new \OC\User\Manager($config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
+ $manager->registerBackend($backend);
+
+ $users = $manager->getByEmail('test@example.com');
+ $this->assertCount(2, $users);
+ $this->assertEquals('uid1', $users[0]->getUID());
+ $this->assertEquals('uid2', $users[1]->getUID());
+ }
+}
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));
+ }
+}
diff --git a/tests/lib/User/UserTest.php b/tests/lib/User/UserTest.php
new file mode 100644
index 00000000000..05056c92193
--- /dev/null
+++ b/tests/lib/User/UserTest.php
@@ -0,0 +1,1032 @@
+<?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\AllConfig;
+use OC\Files\Mount\ObjectHomeMountProvider;
+use OC\Hooks\PublicEmitter;
+use OC\User\Database;
+use OC\User\User;
+use OCP\Comments\ICommentsManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\FileInfo;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\Notification\IManager as INotificationManager;
+use OCP\Notification\INotification;
+use OCP\Server;
+use OCP\UserInterface;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+/**
+ * Class UserTest
+ *
+ * @group DB
+ *
+ * @package Test\User
+ */
+class UserTest extends TestCase {
+ /** @var IEventDispatcher|MockObject */
+ protected $dispatcher;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->dispatcher = Server::get(IEventDispatcher::class);
+ }
+
+ public function testDisplayName(): void {
+ /**
+ * @var \OC\User\Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\OC\User\Backend::class);
+ $backend->expects($this->once())
+ ->method('getDisplayName')
+ ->with($this->equalTo('foo'))
+ ->willReturn('Foo');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
+ ->willReturn(true);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertEquals('Foo', $user->getDisplayName());
+ }
+
+ /**
+ * if the display name contain whitespaces only, we expect the uid as result
+ */
+ public function testDisplayNameEmpty(): void {
+ /**
+ * @var \OC\User\Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\OC\User\Backend::class);
+ $backend->expects($this->once())
+ ->method('getDisplayName')
+ ->with($this->equalTo('foo'))
+ ->willReturn(' ');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
+ ->willReturn(true);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertEquals('foo', $user->getDisplayName());
+ }
+
+ public function testDisplayNameNotSupported(): void {
+ /**
+ * @var \OC\User\Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\OC\User\Backend::class);
+ $backend->expects($this->never())
+ ->method('getDisplayName');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
+ ->willReturn(false);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertEquals('foo', $user->getDisplayName());
+ }
+
+ public function testSetPassword(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('setPassword')
+ ->with($this->equalTo('foo'), $this->equalTo('bar'));
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_PASSWORD) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->setPassword('bar', ''));
+ }
+
+ public function testSetPasswordNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->never())
+ ->method('setPassword');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->setPassword('bar', ''));
+ }
+
+ public function testChangeAvatarSupportedYes(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(AvatarUserDummy::class);
+ $backend->expects($this->once())
+ ->method('canChangeAvatar')
+ ->with($this->equalTo('foo'))
+ ->willReturn(true);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->canChangeAvatar());
+ }
+
+ public function testChangeAvatarSupportedNo(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(AvatarUserDummy::class);
+ $backend->expects($this->once())
+ ->method('canChangeAvatar')
+ ->with($this->equalTo('foo'))
+ ->willReturn(false);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->canChangeAvatar());
+ }
+
+ public function testChangeAvatarNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(AvatarUserDummy::class);
+ $backend->expects($this->never())
+ ->method('canChangeAvatar');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->canChangeAvatar());
+ }
+
+ public function testDelete(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('deleteUser')
+ ->with($this->equalTo('foo'));
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->delete());
+ }
+
+ public function testDeleteWithDifferentHome(): void {
+ /** @var ObjectHomeMountProvider $homeProvider */
+ $homeProvider = Server::get(ObjectHomeMountProvider::class);
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')
+ ->willReturn('foo');
+ if ($homeProvider->getHomeMountForUser($user, $this->createMock(IStorageFactory::class)) !== null) {
+ $this->markTestSkipped('Skipping test for non local home storage');
+ }
+
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $backend->expects($this->once())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::GET_HOME) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ // important: getHome MUST be called before deleteUser because
+ // once the user is deleted, getHome implementations might not
+ // return anything
+ $backend->expects($this->once())
+ ->method('getHome')
+ ->with($this->equalTo('foo'))
+ ->willReturn('/home/foo');
+
+ $backend->expects($this->once())
+ ->method('deleteUser')
+ ->with($this->equalTo('foo'));
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->delete());
+ }
+
+ public function testGetHome(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('getHome')
+ ->with($this->equalTo('foo'))
+ ->willReturn('/home/foo');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::GET_HOME) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertEquals('/home/foo', $user->getHome());
+ }
+
+ public function testGetBackendClassName(): void {
+ $user = new User('foo', new \Test\Util\User\Dummy(), $this->dispatcher);
+ $this->assertEquals('Dummy', $user->getBackendClassName());
+ $user = new User('foo', new Database(), $this->dispatcher);
+ $this->assertEquals('Database', $user->getBackendClassName());
+ }
+
+ public function testGetHomeNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->never())
+ ->method('getHome');
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $allConfig = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $allConfig->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn(true);
+ $allConfig->expects($this->any())
+ ->method('getSystemValueString')
+ ->with($this->equalTo('datadirectory'))
+ ->willReturn('arbitrary/path');
+
+ $user = new User('foo', $backend, $this->dispatcher, null, $allConfig);
+ $this->assertEquals('arbitrary/path/foo', $user->getHome());
+ }
+
+ public function testCanChangePassword(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_PASSWORD) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertTrue($user->canChangePassword());
+ }
+
+ public function testCanChangePasswordNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->canChangePassword());
+ }
+
+ public function testCanChangeDisplayName(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $config = $this->createMock(IConfig::class);
+ $config->method('getSystemValueBool')
+ ->with('allow_user_to_change_display_name')
+ ->willReturn(true);
+
+ $user = new User('foo', $backend, $this->dispatcher, null, $config);
+ $this->assertTrue($user->canChangeDisplayName());
+ }
+
+ public function testCanChangeDisplayNameNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->canChangeDisplayName());
+ }
+
+ public function testSetDisplayNameSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(Database::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $backend->expects($this->once())
+ ->method('setDisplayName')
+ ->with('foo', 'Foo')
+ ->willReturn(true);
+
+ $user = new User('foo', $backend, $this->createMock(IEventDispatcher::class));
+ $this->assertTrue($user->setDisplayName('Foo'));
+ $this->assertEquals('Foo', $user->getDisplayName());
+ }
+
+ /**
+ * don't allow display names containing whitespaces only
+ */
+ public function testSetDisplayNameEmpty(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(Database::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->setDisplayName(' '));
+ $this->assertEquals('foo', $user->getDisplayName());
+ }
+
+ public function testSetDisplayNameNotSupported(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(Database::class);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturn(false);
+
+ $backend->expects($this->never())
+ ->method('setDisplayName');
+
+ $user = new User('foo', $backend, $this->dispatcher);
+ $this->assertFalse($user->setDisplayName('Foo'));
+ $this->assertEquals('foo', $user->getDisplayName());
+ }
+
+ public function testSetPasswordHooks(): void {
+ $hooksCalled = 0;
+ $test = $this;
+
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('setPassword');
+
+ /**
+ * @param User $user
+ * @param string $password
+ */
+ $hook = function ($user, $password) use ($test, &$hooksCalled): void {
+ $hooksCalled++;
+ $test->assertEquals('foo', $user->getUID());
+ $test->assertEquals('bar', $password);
+ };
+
+ $emitter = new PublicEmitter();
+ $emitter->listen('\OC\User', 'preSetPassword', $hook);
+ $emitter->listen('\OC\User', 'postSetPassword', $hook);
+
+ $backend->expects($this->any())
+ ->method('implementsActions')
+ ->willReturnCallback(function ($actions) {
+ if ($actions === \OC\User\Backend::SET_PASSWORD) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher, $emitter);
+
+ $user->setPassword('bar', '');
+ $this->assertEquals(2, $hooksCalled);
+ }
+
+ public static function dataDeleteHooks(): array {
+ return [
+ [true, 2],
+ [false, 1],
+ ];
+ }
+
+ /**
+ * @param bool $result
+ * @param int $expectedHooks
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataDeleteHooks')]
+ public function testDeleteHooks($result, $expectedHooks): void {
+ $hooksCalled = 0;
+ $test = $this;
+
+ /**
+ * @var UserInterface&MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('deleteUser')
+ ->willReturn($result);
+
+ $config = $this->createMock(IConfig::class);
+ $config->method('getSystemValue')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueString')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueBool')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueInt')
+ ->willReturnArgument(1);
+
+ $emitter = new PublicEmitter();
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+
+ /**
+ * @param User $user
+ */
+ $hook = function ($user) use ($test, &$hooksCalled): void {
+ $hooksCalled++;
+ $test->assertEquals('foo', $user->getUID());
+ };
+
+ $emitter->listen('\OC\User', 'preDelete', $hook);
+ $emitter->listen('\OC\User', 'postDelete', $hook);
+
+ $commentsManager = $this->createMock(ICommentsManager::class);
+ $notificationManager = $this->createMock(INotificationManager::class);
+
+ if ($result) {
+ $config->expects($this->atLeastOnce())
+ ->method('deleteAllUserValues')
+ ->with('foo');
+
+ $commentsManager->expects($this->once())
+ ->method('deleteReferencesOfActor')
+ ->with('users', 'foo');
+ $commentsManager->expects($this->once())
+ ->method('deleteReadMarksFromUser')
+ ->with($user);
+
+ $notification = $this->createMock(INotification::class);
+ $notification->expects($this->once())
+ ->method('setUser')
+ ->with('foo');
+
+ $notificationManager->expects($this->once())
+ ->method('createNotification')
+ ->willReturn($notification);
+ $notificationManager->expects($this->once())
+ ->method('markProcessed')
+ ->with($notification);
+ } else {
+ $config->expects($this->never())
+ ->method('deleteAllUserValues');
+
+ $commentsManager->expects($this->never())
+ ->method('deleteReferencesOfActor');
+ $commentsManager->expects($this->never())
+ ->method('deleteReadMarksFromUser');
+
+ $notificationManager->expects($this->never())
+ ->method('createNotification');
+ $notificationManager->expects($this->never())
+ ->method('markProcessed');
+ }
+
+ $this->overwriteService(INotificationManager::class, $notificationManager);
+ $this->overwriteService(ICommentsManager::class, $commentsManager);
+
+ $this->assertSame($result, $user->delete());
+
+ $this->restoreService(AllConfig::class);
+ $this->restoreService(ICommentsManager::class);
+ $this->restoreService(INotificationManager::class);
+
+ $this->assertEquals($expectedHooks, $hooksCalled);
+ }
+
+ public function testDeleteRecoverState() {
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $backend->expects($this->once())
+ ->method('deleteUser')
+ ->willReturn(true);
+
+ $config = $this->createMock(IConfig::class);
+ $config->method('getSystemValue')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueString')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueBool')
+ ->willReturnArgument(1);
+ $config->method('getSystemValueInt')
+ ->willReturnArgument(1);
+
+ $userConfig = [];
+ $config->expects(self::atLeast(2))
+ ->method('setUserValue')
+ ->willReturnCallback(function (): void {
+ $userConfig[] = func_get_args();
+ });
+
+ $commentsManager = $this->createMock(ICommentsManager::class);
+ $commentsManager->expects($this->once())
+ ->method('deleteReferencesOfActor')
+ ->willThrowException(new \Error('Test exception'));
+
+ $this->overwriteService(ICommentsManager::class, $commentsManager);
+ $this->expectException(\Error::class);
+
+ $user = $this->getMockBuilder(User::class)
+ ->onlyMethods(['getHome'])
+ ->setConstructorArgs(['foo', $backend, $this->dispatcher, null, $config])
+ ->getMock();
+
+ $user->expects(self::atLeastOnce())
+ ->method('getHome')
+ ->willReturn('/home/path');
+
+ $user->delete();
+
+ $this->assertEqualsCanonicalizing(
+ [
+ ['foo', 'core', 'deleted', 'true', null],
+ ['foo', 'core', 'deleted.backup-home', '/home/path', null],
+ ],
+ $userConfig,
+ );
+
+ $this->restoreService(ICommentsManager::class);
+ }
+
+ public static function dataGetCloudId(): array {
+ return [
+ ['https://localhost:8888/nextcloud', 'foo@localhost:8888/nextcloud'],
+ ['http://localhost:8888/nextcloud', 'foo@http://localhost:8888/nextcloud'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataGetCloudId')]
+ public function testGetCloudId(string $absoluteUrl, string $cloudId): void {
+ /** @var Backend|MockObject $backend */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+ $urlGenerator = $this->createMock(IURLGenerator::class);
+ $urlGenerator->method('getAbsoluteURL')
+ ->withAnyParameters()
+ ->willReturn($absoluteUrl);
+ $user = new User('foo', $backend, $this->dispatcher, null, null, $urlGenerator);
+ $this->assertEquals($cloudId, $user->getCloudId());
+ }
+
+ public function testSetEMailAddressEmpty(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $test = $this;
+ $hooksCalled = 0;
+
+ /**
+ * @param IUser $user
+ * @param string $feature
+ * @param string $value
+ */
+ $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled): void {
+ $hooksCalled++;
+ $test->assertEquals('eMailAddress', $feature);
+ $test->assertEquals('', $value);
+ };
+
+ $emitter = new PublicEmitter();
+ $emitter->listen('\OC\User', 'changeUser', $hook);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->once())
+ ->method('deleteUserValue')
+ ->with(
+ 'foo',
+ 'settings',
+ 'email'
+ );
+
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+ $user->setEMailAddress('');
+ }
+
+ public function testSetEMailAddress(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $test = $this;
+ $hooksCalled = 0;
+
+ /**
+ * @param IUser $user
+ * @param string $feature
+ * @param string $value
+ */
+ $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled): void {
+ $hooksCalled++;
+ $test->assertEquals('eMailAddress', $feature);
+ $test->assertEquals('foo@bar.com', $value);
+ };
+
+ $emitter = new PublicEmitter();
+ $emitter->listen('\OC\User', 'changeUser', $hook);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->once())
+ ->method('setUserValue')
+ ->with(
+ 'foo',
+ 'settings',
+ 'email',
+ 'foo@bar.com'
+ );
+
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+ $user->setEMailAddress('foo@bar.com');
+ }
+
+ public function testSetEMailAddressNoChange(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ /** @var PublicEmitter|MockObject $emitter */
+ $emitter = $this->createMock(PublicEmitter::class);
+ $emitter->expects($this->never())
+ ->method('emit');
+
+ $dispatcher = $this->createMock(IEventDispatcher::class);
+ $dispatcher->expects($this->never())
+ ->method('dispatch');
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn('foo@bar.com');
+ $config->expects($this->any())
+ ->method('setUserValue');
+
+ $user = new User('foo', $backend, $dispatcher, $emitter, $config);
+ $user->setEMailAddress('foo@bar.com');
+ }
+
+ public function testSetQuota(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $test = $this;
+ $hooksCalled = 0;
+
+ /**
+ * @param IUser $user
+ * @param string $feature
+ * @param string $value
+ */
+ $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled): void {
+ $hooksCalled++;
+ $test->assertEquals('quota', $feature);
+ $test->assertEquals('23 TB', $value);
+ };
+
+ $emitter = new PublicEmitter();
+ $emitter->listen('\OC\User', 'changeUser', $hook);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->once())
+ ->method('setUserValue')
+ ->with(
+ 'foo',
+ 'files',
+ 'quota',
+ '23 TB'
+ );
+
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+ $user->setQuota('23 TB');
+ }
+
+ public function testGetDefaultUnlimitedQuota(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ /** @var PublicEmitter|MockObject $emitter */
+ $emitter = $this->createMock(PublicEmitter::class);
+ $emitter->expects($this->never())
+ ->method('emit');
+
+ $config = $this->createMock(IConfig::class);
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+
+ $userValueMap = [
+ ['foo', 'files', 'quota', 'default', 'default'],
+ ];
+ $appValueMap = [
+ ['files', 'default_quota', 'none', 'none'],
+ // allow unlimited quota
+ ['files', 'allow_unlimited_quota', '1', '1'],
+ ];
+ $config->method('getUserValue')
+ ->willReturnMap($userValueMap);
+ $config->method('getAppValue')
+ ->willReturnMap($appValueMap);
+
+ $this->assertEquals('none', $user->getQuota());
+ $this->assertEquals(FileInfo::SPACE_UNLIMITED, $user->getQuotaBytes());
+ }
+
+ public function testGetDefaultUnlimitedQuotaForbidden(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ /** @var PublicEmitter|MockObject $emitter */
+ $emitter = $this->createMock(PublicEmitter::class);
+ $emitter->expects($this->never())
+ ->method('emit');
+
+ $config = $this->createMock(IConfig::class);
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+
+ $userValueMap = [
+ ['foo', 'files', 'quota', 'default', 'default'],
+ ];
+ $appValueMap = [
+ ['files', 'default_quota', 'none', 'none'],
+ // do not allow unlimited quota
+ ['files', 'allow_unlimited_quota', '1', '0'],
+ ['files', 'quota_preset', '1 GB, 5 GB, 10 GB', '1 GB, 5 GB, 10 GB'],
+ // expect seeing 1 GB used as fallback value
+ ['files', 'default_quota', '1 GB', '1 GB'],
+ ];
+ $config->method('getUserValue')
+ ->willReturnMap($userValueMap);
+ $config->method('getAppValue')
+ ->willReturnMap($appValueMap);
+
+ $this->assertEquals('1 GB', $user->getQuota());
+ $this->assertEquals(1024 * 1024 * 1024, $user->getQuotaBytes());
+ }
+
+ public function testSetQuotaAddressNoChange(): void {
+ /**
+ * @var UserInterface | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ /** @var PublicEmitter|MockObject $emitter */
+ $emitter = $this->createMock(PublicEmitter::class);
+ $emitter->expects($this->never())
+ ->method('emit');
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn('23 TB');
+ $config->expects($this->never())
+ ->method('setUserValue');
+
+ $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
+ $user->setQuota('23 TB');
+ }
+
+ public function testGetLastLogin(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $config = $this->createMock(IConfig::class);
+ $config->method('getUserValue')
+ ->willReturnCallback(function ($uid, $app, $key, $default) {
+ if ($uid === 'foo' && $app === 'login' && $key === 'lastLogin') {
+ return 42;
+ } else {
+ return $default;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher, null, $config);
+ $this->assertSame(42, $user->getLastLogin());
+ }
+
+ public function testSetEnabled(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->once())
+ ->method('setUserValue')
+ ->with(
+ $this->equalTo('foo'),
+ $this->equalTo('core'),
+ $this->equalTo('enabled'),
+ 'true'
+ );
+ /* dav event listener gets the manager list from config */
+ $config->expects(self::any())
+ ->method('getUserValue')
+ ->willReturnCallback(
+ fn ($user, $app, $key, $default) => ($key === 'enabled' ? 'false' : $default)
+ );
+
+ $user = new User('foo', $backend, $this->dispatcher, null, $config);
+ $user->setEnabled(true);
+ }
+
+ public function testSetDisabled(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->once())
+ ->method('setUserValue')
+ ->with(
+ $this->equalTo('foo'),
+ $this->equalTo('core'),
+ $this->equalTo('enabled'),
+ 'false'
+ );
+
+ $user = $this->getMockBuilder(User::class)
+ ->setConstructorArgs([
+ 'foo',
+ $backend,
+ $this->dispatcher,
+ null,
+ $config,
+ ])
+ ->onlyMethods(['isEnabled', 'triggerChange'])
+ ->getMock();
+
+ $user->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn(true);
+ $user->expects($this->once())
+ ->method('triggerChange')
+ ->with(
+ 'enabled',
+ false
+ );
+
+ $user->setEnabled(false);
+ }
+
+ public function testSetDisabledAlreadyDisabled(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $config = $this->createMock(IConfig::class);
+ $config->expects($this->never())
+ ->method('setUserValue');
+
+ $user = $this->getMockBuilder(User::class)
+ ->setConstructorArgs([
+ 'foo',
+ $backend,
+ $this->dispatcher,
+ null,
+ $config,
+ ])
+ ->onlyMethods(['isEnabled', 'triggerChange'])
+ ->getMock();
+
+ $user->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn(false);
+ $user->expects($this->never())
+ ->method('triggerChange');
+
+ $user->setEnabled(false);
+ }
+
+ public function testGetEMailAddress(): void {
+ /**
+ * @var Backend | MockObject $backend
+ */
+ $backend = $this->createMock(\Test\Util\User\Dummy::class);
+
+ $config = $this->createMock(IConfig::class);
+ $config->method('getUserValue')
+ ->willReturnCallback(function ($uid, $app, $key, $default) {
+ if ($uid === 'foo' && $app === 'settings' && $key === 'email') {
+ return 'foo@bar.com';
+ } else {
+ return $default;
+ }
+ });
+
+ $user = new User('foo', $backend, $this->dispatcher, null, $config);
+ $this->assertSame('foo@bar.com', $user->getEMailAddress());
+ }
+}