aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/tests/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/tests/Controller')
-rw-r--r--apps/settings/tests/Controller/AdminSettingsControllerTest.php133
-rw-r--r--apps/settings/tests/Controller/AppSettingsControllerTest.php223
-rw-r--r--apps/settings/tests/Controller/AuthSettingsControllerTest.php440
-rw-r--r--apps/settings/tests/Controller/CheckSetupControllerTest.php581
-rw-r--r--apps/settings/tests/Controller/DelegationControllerTest.php54
-rw-r--r--apps/settings/tests/Controller/MailSettingsControllerTest.php167
-rw-r--r--apps/settings/tests/Controller/TwoFactorSettingsControllerTest.php62
-rw-r--r--apps/settings/tests/Controller/UsersControllerTest.php996
8 files changed, 2656 insertions, 0 deletions
diff --git a/apps/settings/tests/Controller/AdminSettingsControllerTest.php b/apps/settings/tests/Controller/AdminSettingsControllerTest.php
new file mode 100644
index 00000000000..fbdc506457b
--- /dev/null
+++ b/apps/settings/tests/Controller/AdminSettingsControllerTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OCA\Settings\Controller\AdminSettingsController;
+use OCA\Settings\Settings\Personal\ServerDevNotice;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\Group\ISubAdmin;
+use OCP\IGroupManager;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\Server;
+use OCP\Settings\IDeclarativeManager;
+use OCP\Settings\IManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+/**
+ * Class AdminSettingsControllerTest
+ *
+ * @group DB
+ *
+ * @package Tests\Settings\Controller
+ */
+class AdminSettingsControllerTest extends TestCase {
+
+ private IRequest&MockObject $request;
+ private INavigationManager&MockObject $navigationManager;
+ private IManager&MockObject $settingsManager;
+ private IUserSession&MockObject $userSession;
+ private IGroupManager&MockObject $groupManager;
+ private ISubAdmin&MockObject $subAdmin;
+ private IDeclarativeManager&MockObject $declarativeSettingsManager;
+ private IInitialState&MockObject $initialState;
+
+ private string $adminUid = 'lololo';
+ private AdminSettingsController $adminSettingsController;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->navigationManager = $this->createMock(INavigationManager::class);
+ $this->settingsManager = $this->createMock(IManager::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->groupManager = $this->createMock(IGroupManager::class);
+ $this->subAdmin = $this->createMock(ISubAdmin::class);
+ $this->declarativeSettingsManager = $this->createMock(IDeclarativeManager::class);
+ $this->initialState = $this->createMock(IInitialState::class);
+
+ $this->adminSettingsController = new AdminSettingsController(
+ 'settings',
+ $this->request,
+ $this->navigationManager,
+ $this->settingsManager,
+ $this->userSession,
+ $this->groupManager,
+ $this->subAdmin,
+ $this->declarativeSettingsManager,
+ $this->initialState,
+ );
+
+ $user = Server::get(IUserManager::class)->createUser($this->adminUid, 'mylongrandompassword');
+ \OC_User::setUserId($user->getUID());
+ Server::get(IGroupManager::class)->createGroup('admin')->addUser($user);
+ }
+
+ protected function tearDown(): void {
+ Server::get(IUserManager::class)
+ ->get($this->adminUid)
+ ->delete();
+ \OC_User::setUserId(null);
+ Server::get(IUserSession::class)->setUser(null);
+
+ parent::tearDown();
+ }
+
+ public function testIndex(): void {
+ $user = $this->createMock(IUser::class);
+ $this->userSession
+ ->method('getUser')
+ ->willReturn($user);
+ $user->method('getUID')->willReturn('user123');
+ $this->groupManager
+ ->method('isAdmin')
+ ->with('user123')
+ ->willReturn(true);
+ $this->subAdmin
+ ->method('isSubAdmin')
+ ->with($user)
+ ->willReturn(false);
+
+ $form = new TemplateResponse('settings', 'settings/empty');
+ $setting = $this->createMock(ServerDevNotice::class);
+ $setting->expects(self::any())
+ ->method('getForm')
+ ->willReturn($form);
+ $this->settingsManager
+ ->expects($this->once())
+ ->method('getAdminSections')
+ ->willReturn([]);
+ $this->settingsManager
+ ->expects($this->once())
+ ->method('getPersonalSections')
+ ->willReturn([]);
+ $this->settingsManager
+ ->expects($this->once())
+ ->method('getAllowedAdminSettings')
+ ->with('test')
+ ->willReturn([5 => [$setting]]);
+ $this->declarativeSettingsManager
+ ->expects($this->any())
+ ->method('getFormIDs')
+ ->with($user, 'admin', 'test')
+ ->willReturn([]);
+
+ $idx = $this->adminSettingsController->index('test');
+
+ $expected = new TemplateResponse('settings', 'settings/frame', [
+ 'forms' => ['personal' => [], 'admin' => []],
+ 'content' => ''
+ ]);
+ $this->assertEquals($expected, $idx);
+ }
+}
diff --git a/apps/settings/tests/Controller/AppSettingsControllerTest.php b/apps/settings/tests/Controller/AppSettingsControllerTest.php
new file mode 100644
index 00000000000..392bb7b561d
--- /dev/null
+++ b/apps/settings/tests/Controller/AppSettingsControllerTest.php
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2015 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OC\App\AppManager;
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\App\AppStore\Fetcher\AppDiscoverFetcher;
+use OC\App\AppStore\Fetcher\AppFetcher;
+use OC\App\AppStore\Fetcher\CategoryFetcher;
+use OC\Installer;
+use OCA\Settings\Controller\AppSettingsController;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\L10N\IFactory;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class AppSettingsControllerTest
+ *
+ * @package Tests\Settings\Controller
+ *
+ * @group DB
+ */
+class AppSettingsControllerTest extends TestCase {
+ private IRequest&MockObject $request;
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private INavigationManager&MockObject $navigationManager;
+ private AppManager&MockObject $appManager;
+ private CategoryFetcher&MockObject $categoryFetcher;
+ private AppFetcher&MockObject $appFetcher;
+ private IFactory&MockObject $l10nFactory;
+ private BundleFetcher&MockObject $bundleFetcher;
+ private Installer&MockObject $installer;
+ private IURLGenerator&MockObject $urlGenerator;
+ private LoggerInterface&MockObject $logger;
+ private IInitialState&MockObject $initialState;
+ private IAppDataFactory&MockObject $appDataFactory;
+ private AppDiscoverFetcher&MockObject $discoverFetcher;
+ private IClientService&MockObject $clientService;
+ private AppSettingsController $appSettingsController;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->appDataFactory = $this->createMock(IAppDataFactory::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnArgument(0);
+ $this->config = $this->createMock(IConfig::class);
+ $this->navigationManager = $this->createMock(INavigationManager::class);
+ $this->appManager = $this->createMock(AppManager::class);
+ $this->categoryFetcher = $this->createMock(CategoryFetcher::class);
+ $this->appFetcher = $this->createMock(AppFetcher::class);
+ $this->l10nFactory = $this->createMock(IFactory::class);
+ $this->bundleFetcher = $this->createMock(BundleFetcher::class);
+ $this->installer = $this->createMock(Installer::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->initialState = $this->createMock(IInitialState::class);
+ $this->discoverFetcher = $this->createMock(AppDiscoverFetcher::class);
+ $this->clientService = $this->createMock(IClientService::class);
+
+ $this->appSettingsController = new AppSettingsController(
+ 'settings',
+ $this->request,
+ $this->appDataFactory,
+ $this->l10n,
+ $this->config,
+ $this->navigationManager,
+ $this->appManager,
+ $this->categoryFetcher,
+ $this->appFetcher,
+ $this->l10nFactory,
+ $this->bundleFetcher,
+ $this->installer,
+ $this->urlGenerator,
+ $this->logger,
+ $this->initialState,
+ $this->discoverFetcher,
+ $this->clientService,
+ );
+ }
+
+ public function testListCategories(): void {
+ $this->installer->expects($this->any())
+ ->method('isUpdateAvailable')
+ ->willReturn(false);
+ $expected = new JSONResponse([
+ [
+ 'id' => 'auth',
+ 'displayName' => 'Authentication & authorization',
+ ],
+ [
+ 'id' => 'customization',
+ 'displayName' => 'Customization',
+ ],
+ [
+ 'id' => 'files',
+ 'displayName' => 'Files',
+ ],
+ [
+ 'id' => 'integration',
+ 'displayName' => 'Integration',
+ ],
+ [
+ 'id' => 'monitoring',
+ 'displayName' => 'Monitoring',
+ ],
+ [
+ 'id' => 'multimedia',
+ 'displayName' => 'Multimedia',
+ ],
+ [
+ 'id' => 'office',
+ 'displayName' => 'Office & text',
+ ],
+ [
+ 'id' => 'organization',
+ 'displayName' => 'Organization',
+ ],
+ [
+ 'id' => 'social',
+ 'displayName' => 'Social & communication',
+ ],
+ [
+ 'id' => 'tools',
+ 'displayName' => 'Tools',
+ ],
+ ]);
+
+ $this->categoryFetcher
+ ->expects($this->once())
+ ->method('get')
+ ->willReturn(json_decode('[{"id":"auth","translations":{"cs":{"name":"Autentizace & autorizace","description":"Aplikace poskytující služby dodatečného ověření nebo přihlášení"},"hu":{"name":"Azonosítás és hitelesítés","description":"Apps that provide additional authentication or authorization services"},"de":{"name":"Authentifizierung & Authorisierung","description":"Apps die zusätzliche Autentifizierungs- oder Autorisierungsdienste bereitstellen"},"nl":{"name":"Authenticatie & authorisatie","description":"Apps die aanvullende authenticatie- en autorisatiediensten bieden"},"nb":{"name":"Pålogging og tilgangsstyring","description":"Apper for å tilby ekstra pålogging eller tilgangsstyring"},"it":{"name":"Autenticazione e autorizzazione","description":"Apps that provide additional authentication or authorization services"},"fr":{"name":"Authentification et autorisations","description":"Applications qui fournissent des services d\'authentification ou d\'autorisations additionnels."},"ru":{"name":"Аутентификация и авторизация","description":"Apps that provide additional authentication or authorization services"},"en":{"name":"Authentication & authorization","description":"Apps that provide additional authentication or authorization services"}}},{"id":"customization","translations":{"cs":{"name":"Přizpůsobení","description":"Motivy a aplikace měnící rozvržení a uživatelské rozhraní"},"it":{"name":"Personalizzazione","description":"Applicazioni di temi, modifiche della disposizione e UX"},"de":{"name":"Anpassung","description":"Apps zur Änderung von Themen, Layout und Benutzererfahrung"},"hu":{"name":"Személyre szabás","description":"Témák, elrendezések felhasználói felület módosító alkalmazások"},"nl":{"name":"Maatwerk","description":"Thema\'s, layout en UX aanpassingsapps"},"nb":{"name":"Tilpasning","description":"Apper for å endre Tema, utseende og brukeropplevelse"},"fr":{"name":"Personalisation","description":"Thèmes, apparence et applications modifiant l\'expérience utilisateur"},"ru":{"name":"Настройка","description":"Themes, layout and UX change apps"},"en":{"name":"Customization","description":"Themes, layout and UX change apps"}}},{"id":"files","translations":{"cs":{"name":"Soubory","description":"Aplikace rozšiřující správu souborů nebo aplikaci Soubory"},"it":{"name":"File","description":"Applicazioni di gestione dei file ed estensione dell\'applicazione FIle"},"de":{"name":"Dateien","description":"Dateimanagement sowie Erweiterungs-Apps für die Dateien-App"},"hu":{"name":"Fájlok","description":"Fájl kezelő és kiegészítő alkalmazások"},"nl":{"name":"Bestanden","description":"Bestandebeheer en uitbreidingen van bestand apps"},"nb":{"name":"Filer","description":"Apper for filhåndtering og filer"},"fr":{"name":"Fichiers","description":"Applications de gestion de fichiers et extensions à l\'application Fichiers"},"ru":{"name":"Файлы","description":"Расширение: файлы и управление файлами"},"en":{"name":"Files","description":"File management and Files app extension apps"}}},{"id":"integration","translations":{"it":{"name":"Integrazione","description":"Applicazioni che collegano Nextcloud con altri servizi e piattaforme"},"hu":{"name":"Integráció","description":"Apps that connect Nextcloud with other services and platforms"},"nl":{"name":"Integratie","description":"Apps die Nextcloud verbinden met andere services en platformen"},"nb":{"name":"Integrasjon","description":"Apper som kobler Nextcloud med andre tjenester og plattformer"},"de":{"name":"Integration","description":"Apps die Nextcloud mit anderen Diensten und Plattformen verbinden"},"cs":{"name":"Propojení","description":"Aplikace propojující NextCloud s dalšími službami a platformami"},"fr":{"name":"Intégration","description":"Applications qui connectent Nextcloud avec d\'autres services et plateformes"},"ru":{"name":"Интеграция","description":"Приложения, соединяющие Nextcloud с другими службами и платформами"},"en":{"name":"Integration","description":"Apps that connect Nextcloud with other services and platforms"}}},{"id":"monitoring","translations":{"nb":{"name":"Overvåking","description":"Apper for statistikk, systemdiagnose og aktivitet"},"it":{"name":"Monitoraggio","description":"Applicazioni di statistiche, diagnostica di sistema e attività"},"de":{"name":"Überwachung","description":"Datenstatistiken-, Systemdiagnose- und Aktivitäten-Apps"},"hu":{"name":"Megfigyelés","description":"Data statistics, system diagnostics and activity apps"},"nl":{"name":"Monitoren","description":"Gegevensstatistiek, systeem diagnose en activiteit apps"},"cs":{"name":"Kontrola","description":"Datové statistiky, diagnózy systému a aktivity aplikací"},"fr":{"name":"Surveillance","description":"Applications de statistiques sur les données, de diagnostics systèmes et d\'activité."},"ru":{"name":"Мониторинг","description":"Статистика данных, диагностика системы и активность приложений"},"en":{"name":"Monitoring","description":"Data statistics, system diagnostics and activity apps"}}},{"id":"multimedia","translations":{"nb":{"name":"Multimedia","description":"Apper for lyd, film og bilde"},"it":{"name":"Multimedia","description":"Applicazioni per audio, video e immagini"},"de":{"name":"Multimedia","description":"Audio-, Video- und Bilder-Apps"},"hu":{"name":"Multimédia","description":"Hang, videó és kép alkalmazások"},"nl":{"name":"Multimedia","description":"Audio, video en afbeelding apps"},"en":{"name":"Multimedia","description":"Audio, video and picture apps"},"cs":{"name":"Multimédia","description":"Aplikace audia, videa a obrázků"},"fr":{"name":"Multimédia","description":"Applications audio, vidéo et image"},"ru":{"name":"Мультимедиа","description":"Приложение аудио, видео и изображения"}}},{"id":"office","translations":{"nb":{"name":"Kontorstøtte og tekst","description":"Apper for Kontorstøtte og tekstbehandling"},"it":{"name":"Ufficio e testo","description":"Applicazione per ufficio ed elaborazione di testi"},"de":{"name":"Büro & Text","description":"Büro- und Textverarbeitungs-Apps"},"hu":{"name":"Iroda és szöveg","description":"Irodai és szöveg feldolgozó alkalmazások"},"nl":{"name":"Office & tekst","description":"Office en tekstverwerkingsapps"},"cs":{"name":"Kancelář a text","description":"Aplikace pro kancelář a zpracování textu"},"fr":{"name":"Bureautique & texte","description":"Applications de bureautique et de traitement de texte"},"en":{"name":"Office & text","description":"Office and text processing apps"}}},{"id":"organization","translations":{"nb":{"name":"Organisering","description":"Apper for tidsstyring, oppgaveliste og kalender"},"it":{"name":"Organizzazione","description":"Applicazioni di gestione del tempo, elenco delle cose da fare e calendario"},"hu":{"name":"Szervezet","description":"Időbeosztás, teendő lista és naptár alkalmazások"},"nl":{"name":"Organisatie","description":"Tijdmanagement, takenlijsten en agenda apps"},"cs":{"name":"Organizace","description":"Aplikace pro správu času, plánování a kalendáře"},"de":{"name":"Organisation","description":"Time management, Todo list and calendar apps"},"fr":{"name":"Organisation","description":"Applications de gestion du temps, de listes de tâches et d\'agendas"},"ru":{"name":"Организация","description":"Приложения по управлению временем, список задач и календарь"},"en":{"name":"Organization","description":"Time management, Todo list and calendar apps"}}},{"id":"social","translations":{"nb":{"name":"Sosialt og kommunikasjon","description":"Apper for meldinger, kontakthåndtering og sosiale medier"},"it":{"name":"Sociale e comunicazione","description":"Applicazioni di messaggistica, gestione dei contatti e reti sociali"},"de":{"name":"Kommunikation","description":"Nachrichten-, Kontaktverwaltungs- und Social-Media-Apps"},"hu":{"name":"Közösségi és kommunikáció","description":"Üzenetküldő, kapcsolat kezelő és közösségi média alkalmazások"},"nl":{"name":"Sociaal & communicatie","description":"Messaging, contactbeheer en social media apps"},"cs":{"name":"Sociální sítě a komunikace","description":"Aplikace pro zasílání zpráv, správu kontaktů a sociální sítě"},"fr":{"name":"Social & communication","description":"Applications de messagerie, de gestion de contacts et de réseaux sociaux"},"ru":{"name":"Социальное и связь","description":"Общение, управление контактами и социальное медиа-приложение"},"en":{"name":"Social & communication","description":"Messaging, contact management and social media apps"}}},{"id":"tools","translations":{"nb":{"name":"Verktøy","description":"Alt annet"},"it":{"name":"Strumenti","description":"Tutto il resto"},"hu":{"name":"Eszközök","description":"Minden más"},"nl":{"name":"Tools","description":"De rest"},"de":{"name":"Werkzeuge","description":"Alles Andere"},"en":{"name":"Tools","description":"Everything else"},"cs":{"name":"Nástroje","description":"Vše ostatní"},"fr":{"name":"Outils","description":"Tout le reste"},"ru":{"name":"Приложения","description":"Что-то еще"}}}]', true));
+
+ $this->assertEquals($expected, $this->appSettingsController->listCategories());
+ }
+
+ public function testViewApps(): void {
+ $this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
+ $this->installer->expects($this->any())
+ ->method('isUpdateAvailable')
+ ->willReturn(false);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('appstoreenabled', true)
+ ->willReturn(true);
+ $this->navigationManager
+ ->expects($this->once())
+ ->method('setActiveEntry')
+ ->with('core_apps');
+
+ $this->initialState
+ ->expects($this->exactly(4))
+ ->method('provideInitialState');
+
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
+
+ $expected = new TemplateResponse('settings',
+ 'settings/empty',
+ [
+ 'pageTitle' => 'Settings'
+ ],
+ 'user');
+ $expected->setContentSecurityPolicy($policy);
+
+ $this->assertEquals($expected, $this->appSettingsController->viewApps());
+ }
+
+ public function testViewAppsAppstoreNotEnabled(): void {
+ $this->installer->expects($this->any())
+ ->method('isUpdateAvailable')
+ ->willReturn(false);
+ $this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('appstoreenabled', true)
+ ->willReturn(false);
+ $this->navigationManager
+ ->expects($this->once())
+ ->method('setActiveEntry')
+ ->with('core_apps');
+
+ $this->initialState
+ ->expects($this->exactly(4))
+ ->method('provideInitialState');
+
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
+
+ $expected = new TemplateResponse('settings',
+ 'settings/empty',
+ [
+ 'pageTitle' => 'Settings'
+ ],
+ 'user');
+ $expected->setContentSecurityPolicy($policy);
+
+ $this->assertEquals($expected, $this->appSettingsController->viewApps());
+ }
+}
diff --git a/apps/settings/tests/Controller/AuthSettingsControllerTest.php b/apps/settings/tests/Controller/AuthSettingsControllerTest.php
new file mode 100644
index 00000000000..d195dbf83d3
--- /dev/null
+++ b/apps/settings/tests/Controller/AuthSettingsControllerTest.php
@@ -0,0 +1,440 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace Test\Settings\Controller;
+
+use OC\AppFramework\Http;
+use OC\Authentication\Exceptions\ExpiredTokenException;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\IWipeableToken;
+use OC\Authentication\Token\PublicKeyToken;
+use OC\Authentication\Token\RemoteWipe;
+use OCA\Settings\Controller\AuthSettingsController;
+use OCP\Activity\IEvent;
+use OCP\Activity\IManager;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IUserSession;
+use OCP\Security\ISecureRandom;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class AuthSettingsControllerTest extends TestCase {
+ private IRequest&MockObject $request;
+ private IProvider&MockObject $tokenProvider;
+ private ISession&MockObject $session;
+ private IUserSession&MockObject $userSession;
+ private ISecureRandom&MockObject $secureRandom;
+ private IManager&MockObject $activityManager;
+ private RemoteWipe&MockObject $remoteWipe;
+ private string $uid = 'jane';
+ private AuthSettingsController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->tokenProvider = $this->createMock(IProvider::class);
+ $this->session = $this->createMock(ISession::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->secureRandom = $this->createMock(ISecureRandom::class);
+ $this->activityManager = $this->createMock(IManager::class);
+ $this->remoteWipe = $this->createMock(RemoteWipe::class);
+ /** @var LoggerInterface&MockObject $logger */
+ $logger = $this->createMock(LoggerInterface::class);
+
+ $this->controller = new AuthSettingsController(
+ 'core',
+ $this->request,
+ $this->tokenProvider,
+ $this->session,
+ $this->secureRandom,
+ $this->uid,
+ $this->userSession,
+ $this->activityManager,
+ $this->remoteWipe,
+ $logger
+ );
+ }
+
+ public function testCreate(): void {
+ $name = 'Nexus 4';
+ $sessionToken = $this->createMock(IToken::class);
+ $deviceToken = $this->createMock(IToken::class);
+ $password = '123456';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sessionid');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sessionid')
+ ->willReturn($sessionToken);
+ $this->tokenProvider->expects($this->once())
+ ->method('getPassword')
+ ->with($sessionToken, 'sessionid')
+ ->willReturn($password);
+ $sessionToken->expects($this->once())
+ ->method('getLoginName')
+ ->willReturn('User13');
+
+ $this->secureRandom->expects($this->exactly(5))
+ ->method('generate')
+ ->with(5, ISecureRandom::CHAR_HUMAN_READABLE)
+ ->willReturn('XXXXX');
+ $newToken = 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX';
+
+ $this->tokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with($newToken, $this->uid, 'User13', $password, $name, IToken::PERMANENT_TOKEN)
+ ->willReturn($deviceToken);
+
+ $deviceToken->expects($this->once())
+ ->method('jsonSerialize')
+ ->willReturn(['dummy' => 'dummy', 'canDelete' => true]);
+
+ $this->mockActivityManager();
+
+ $expected = [
+ 'token' => $newToken,
+ 'deviceToken' => ['dummy' => 'dummy', 'canDelete' => true, 'canRename' => true],
+ 'loginName' => 'User13',
+ ];
+
+ $response = $this->controller->create($name);
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals($expected, $response->getData());
+ }
+
+ public function testCreateSessionNotAvailable(): void {
+ $name = 'personal phone';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willThrowException(new SessionNotAvailableException());
+
+ $expected = new JSONResponse();
+ $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+
+ $this->assertEquals($expected, $this->controller->create($name));
+ }
+
+ public function testCreateInvalidToken(): void {
+ $name = 'Company IPhone';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sessionid');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sessionid')
+ ->willThrowException(new InvalidTokenException());
+
+ $expected = new JSONResponse();
+ $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+
+ $this->assertEquals($expected, $this->controller->create($name));
+ }
+
+ public function testDestroy(): void {
+ $tokenId = 124;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+ $this->mockActivityManager();
+
+ $token->expects($this->exactly(2))
+ ->method('getId')
+ ->willReturn($tokenId);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('jane');
+
+ $this->tokenProvider->expects($this->once())
+ ->method('invalidateTokenById')
+ ->with($this->uid, $tokenId);
+
+ $this->assertEquals([], $this->controller->destroy($tokenId));
+ }
+
+ public function testDestroyExpired(): void {
+ $tokenId = 124;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $token->expects($this->exactly(2))
+ ->method('getId')
+ ->willReturn($tokenId);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->uid);
+
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo($tokenId))
+ ->willThrowException(new ExpiredTokenException($token));
+
+ $this->tokenProvider->expects($this->once())
+ ->method('invalidateTokenById')
+ ->with($this->uid, $tokenId);
+
+ $this->assertSame([], $this->controller->destroy($tokenId));
+ }
+
+ public function testDestroyWrongUser(): void {
+ $tokenId = 124;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('foobar');
+
+ $response = $this->controller->destroy($tokenId);
+ $this->assertSame([], $response->getData());
+ $this->assertSame(\OCP\AppFramework\Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ public static function dataRenameToken(): array {
+ return [
+ 'App password => Other token name' => ['App password', 'Other token name'],
+ 'Other token name => App password' => ['Other token name', 'App password'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataRenameToken')]
+ public function testUpdateRename(string $name, string $newName): void {
+ $tokenId = 42;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+ $this->mockActivityManager();
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('jane');
+
+ $token->expects($this->once())
+ ->method('getName')
+ ->willReturn($name);
+
+ $token->expects($this->once())
+ ->method('getScopeAsArray')
+ ->willReturn([IToken::SCOPE_FILESYSTEM => true]);
+
+ $token->expects($this->once())
+ ->method('setName')
+ ->with($this->equalTo($newName));
+
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($this->equalTo($token));
+
+ $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], $newName));
+ }
+
+ public static function dataUpdateFilesystemScope(): array {
+ return [
+ 'Grant filesystem access' => [false, true],
+ 'Revoke filesystem access' => [true, false],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateFilesystemScope')]
+ public function testUpdateFilesystemScope(bool $filesystem, bool $newFilesystem): void {
+ $tokenId = 42;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+ $this->mockActivityManager();
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('jane');
+
+ $token->expects($this->once())
+ ->method('getName')
+ ->willReturn('App password');
+
+ $token->expects($this->once())
+ ->method('getScopeAsArray')
+ ->willReturn([IToken::SCOPE_FILESYSTEM => $filesystem]);
+
+ $token->expects($this->once())
+ ->method('setScope')
+ ->with($this->equalTo([IToken::SCOPE_FILESYSTEM => $newFilesystem]));
+
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($this->equalTo($token));
+
+ $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => $newFilesystem], 'App password'));
+ }
+
+ public function testUpdateNoChange(): void {
+ $tokenId = 42;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('jane');
+
+ $token->expects($this->once())
+ ->method('getName')
+ ->willReturn('App password');
+
+ $token->expects($this->once())
+ ->method('getScopeAsArray')
+ ->willReturn([IToken::SCOPE_FILESYSTEM => true]);
+
+ $token->expects($this->never())
+ ->method('setName');
+
+ $token->expects($this->never())
+ ->method('setScope');
+
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($this->equalTo($token));
+
+ $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password'));
+ }
+
+ public function testUpdateExpired(): void {
+ $tokenId = 42;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->uid);
+
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo($tokenId))
+ ->willThrowException(new ExpiredTokenException($token));
+
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($this->equalTo($token));
+
+ $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password'));
+ }
+
+ public function testUpdateTokenWrongUser(): void {
+ $tokenId = 42;
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mockGetTokenById($tokenId, $token);
+
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('foobar');
+
+ $token->expects($this->never())
+ ->method('setScope');
+ $this->tokenProvider->expects($this->never())
+ ->method('updateToken');
+
+ $response = $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password');
+ $this->assertSame([], $response->getData());
+ $this->assertSame(\OCP\AppFramework\Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ public function testUpdateTokenNonExisting(): void {
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo(42))
+ ->willThrowException(new InvalidTokenException('Token does not exist'));
+
+ $this->tokenProvider->expects($this->never())
+ ->method('updateToken');
+
+ $response = $this->controller->update(42, [IToken::SCOPE_FILESYSTEM => true], 'App password');
+ $this->assertSame([], $response->getData());
+ $this->assertSame(\OCP\AppFramework\Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ private function mockActivityManager(): void {
+ $this->activityManager->expects($this->once())
+ ->method('generateEvent')
+ ->willReturn($this->createMock(IEvent::class));
+ $this->activityManager->expects($this->once())
+ ->method('publish');
+ }
+
+ /**
+ * @param int $tokenId
+ * @param $token
+ */
+ private function mockGetTokenById(int $tokenId, $token): void {
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo($tokenId))
+ ->willReturn($token);
+ }
+
+ public function testRemoteWipeNotSuccessful(): void {
+ $token = $this->createMock(IToken::class);
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->uid);
+ $this->mockGetTokenById(123, $token);
+
+ $this->remoteWipe->expects($this->once())
+ ->method('markTokenForWipe')
+ ->with($token)
+ ->willReturn(false);
+
+ $response = $this->controller->wipe(123);
+
+ $expected = new JSONResponse([], Http::STATUS_BAD_REQUEST);
+ $this->assertEquals($expected, $response);
+ }
+
+ public function testRemoteWipeWrongUser(): void {
+ $token = $this->createMock(IToken::class);
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn('definetly-not-' . $this->uid);
+ $this->mockGetTokenById(123, $token);
+
+ $this->remoteWipe->expects($this->never())
+ ->method('markTokenForWipe');
+
+ $response = $this->controller->wipe(123);
+
+ $expected = new JSONResponse([], Http::STATUS_NOT_FOUND);
+ $this->assertEquals($expected, $response);
+ }
+
+ public function testRemoteWipeSuccessful(): void {
+ $token = $this->createMock(IWipeableToken::class);
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->uid);
+ $this->mockGetTokenById(123, $token);
+
+ $this->remoteWipe->expects($this->once())
+ ->method('markTokenForWipe')
+ ->with($token)
+ ->willReturn(true);
+
+ $response = $this->controller->wipe(123);
+
+ $expected = new JSONResponse([]);
+ $this->assertEquals($expected, $response);
+ }
+}
diff --git a/apps/settings/tests/Controller/CheckSetupControllerTest.php b/apps/settings/tests/Controller/CheckSetupControllerTest.php
new file mode 100644
index 00000000000..a8e89260573
--- /dev/null
+++ b/apps/settings/tests/Controller/CheckSetupControllerTest.php
@@ -0,0 +1,581 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2015 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OC\IntegrityCheck\Checker;
+use OCA\Settings\Controller\CheckSetupController;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataDisplayResponse;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\ISetupCheckManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class CheckSetupControllerTest
+ *
+ * @backupStaticAttributes
+ * @package Tests\Settings\Controller
+ */
+class CheckSetupControllerTest extends TestCase {
+ private IRequest&MockObject $request;
+ private IConfig&MockObject $config;
+ private IURLGenerator&MockObject $urlGenerator;
+ private IL10N&MockObject $l10n;
+ private LoggerInterface&MockObject $logger;
+ private Checker&MockObject $checker;
+ private ISetupCheckManager&MockObject $setupCheckManager;
+ private CheckSetupController $checkSetupController;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ $this->checker = $this->createMock(Checker::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->setupCheckManager = $this->createMock(ISetupCheckManager::class);
+ $this->checkSetupController = new CheckSetupController(
+ 'settings',
+ $this->request,
+ $this->config,
+ $this->urlGenerator,
+ $this->l10n,
+ $this->checker,
+ $this->logger,
+ $this->setupCheckManager,
+ );
+ }
+
+ public function testCheck(): void {
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->willReturnMap([
+ ['files_external', 'user_certificate_scan', '', '["a", "b"]'],
+ ['dav', 'needs_system_address_book_sync', 'no', 'no'],
+ ]);
+ $this->config->expects($this->any())
+ ->method('getSystemValue')
+ ->willReturnMap([
+ ['connectivity_check_domains', ['www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'], ['www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org']],
+ ['memcache.local', null, 'SomeProvider'],
+ ['has_internet_connection', true, true],
+ ['appstoreenabled', true, false],
+ ]);
+
+ $this->request->expects($this->never())
+ ->method('getHeader');
+
+ $this->urlGenerator->method('linkToDocs')
+ ->willReturnCallback(function (string $key): string {
+ if ($key === 'admin-performance') {
+ return 'http://docs.example.org/server/go.php?to=admin-performance';
+ }
+ if ($key === 'admin-security') {
+ return 'https://docs.example.org/server/8.1/admin_manual/configuration_server/hardening.html';
+ }
+ if ($key === 'admin-reverse-proxy') {
+ return 'reverse-proxy-doc-link';
+ }
+ if ($key === 'admin-code-integrity') {
+ return 'http://docs.example.org/server/go.php?to=admin-code-integrity';
+ }
+ if ($key === 'admin-db-conversion') {
+ return 'http://docs.example.org/server/go.php?to=admin-db-conversion';
+ }
+ return '';
+ });
+
+ $this->urlGenerator->method('getAbsoluteURL')
+ ->willReturnCallback(function (string $url): string {
+ if ($url === 'index.php/settings/admin') {
+ return 'https://server/index.php/settings/admin';
+ }
+ if ($url === 'index.php') {
+ return 'https://server/index.php';
+ }
+ return '';
+ });
+
+ $expected = new DataResponse(
+ [
+ 'generic' => [],
+ ]
+ );
+ $this->assertEquals($expected, $this->checkSetupController->check());
+ }
+
+ public function testRescanFailedIntegrityCheck(): void {
+ $this->checker
+ ->expects($this->once())
+ ->method('runInstanceVerification');
+ $this->urlGenerator
+ ->expects($this->once())
+ ->method('linkToRoute')
+ ->with('settings.AdminSettings.index')
+ ->willReturn('/admin');
+
+ $expected = new RedirectResponse('/admin');
+ $this->assertEquals($expected, $this->checkSetupController->rescanFailedIntegrityCheck());
+ }
+
+ public function testGetFailedIntegrityCheckDisabled(): void {
+ $this->checker
+ ->expects($this->once())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(false);
+
+ $expected = new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
+ $this->assertEquals($expected, $this->checkSetupController->getFailedIntegrityCheckFiles());
+ }
+
+
+ public function testGetFailedIntegrityCheckFilesWithNoErrorsFound(): void {
+ $this->checker
+ ->expects($this->once())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker
+ ->expects($this->once())
+ ->method('getResults')
+ ->willReturn([]);
+
+ $expected = new DataDisplayResponse(
+ 'No errors have been found.',
+ Http::STATUS_OK,
+ [
+ 'Content-Type' => 'text/plain',
+ ]
+ );
+ $this->assertEquals($expected, $this->checkSetupController->getFailedIntegrityCheckFiles());
+ }
+
+ public function testGetFailedIntegrityCheckFilesWithSomeErrorsFound(): void {
+ $this->checker
+ ->expects($this->once())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker
+ ->expects($this->once())
+ ->method('getResults')
+ ->willReturn([ 'core' => [ 'EXTRA_FILE' => ['/testfile' => []], 'INVALID_HASH' => [ '/.idea/workspace.xml' => [ 'expected' => 'f1c5e2630d784bc9cb02d5a28f55d6f24d06dae2a0fee685f3c2521b050955d9d452769f61454c9ddfa9c308146ade10546cfa829794448eaffbc9a04a29d216', 'current' => 'ce08bf30bcbb879a18b49239a9bec6b8702f52452f88a9d32142cad8d2494d5735e6bfa0d8642b2762c62ca5be49f9bf4ec231d4a230559d4f3e2c471d3ea094', ], '/lib/private/integritycheck/checker.php' => [ 'expected' => 'c5a03bacae8dedf8b239997901ba1fffd2fe51271d13a00cc4b34b09cca5176397a89fc27381cbb1f72855fa18b69b6f87d7d5685c3b45aee373b09be54742ea', 'current' => '88a3a92c11db91dec1ac3be0e1c87f862c95ba6ffaaaa3f2c3b8f682187c66f07af3a3b557a868342ef4a271218fe1c1e300c478e6c156c5955ed53c40d06585', ], '/settings/controller/checksetupcontroller.php' => [ 'expected' => '3e1de26ce93c7bfe0ede7c19cb6c93cadc010340225b375607a7178812e9de163179b0dc33809f451e01f491d93f6f5aaca7929685d21594cccf8bda732327c4', 'current' => '09563164f9904a837f9ca0b5f626db56c838e5098e0ccc1d8b935f68fa03a25c5ec6f6b2d9e44a868e8b85764dafd1605522b4af8db0ae269d73432e9a01e63a', ], ], ], 'bookmarks' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'dav' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'encryption' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'external' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'federation' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_antivirus' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_drop' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_external' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_pdfviewer' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_sharing' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_trashbin' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_versions' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'files_videoviewer' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'firstrunwizard' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'gitsmart' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'logreader' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature could not get verified.', ], ], 'password_policy' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'provisioning_api' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'sketch' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'threatblock' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'two_factor_auth' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'user_ldap' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], 'user_shibboleth' => [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', 'message' => 'Signature data not found.', ], ], ]);
+
+ $expected = new DataDisplayResponse(
+ 'Technical information
+=====================
+The following list covers which files have failed the integrity check. Please read
+the previous linked documentation to learn more about the errors and how to fix
+them.
+
+Results
+=======
+- core
+ - EXTRA_FILE
+ - /testfile
+ - INVALID_HASH
+ - /.idea/workspace.xml
+ - /lib/private/integritycheck/checker.php
+ - /settings/controller/checksetupcontroller.php
+- bookmarks
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- dav
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- encryption
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- external
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- federation
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_antivirus
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_drop
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_external
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_pdfviewer
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_sharing
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_trashbin
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_versions
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- files_videoviewer
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- firstrunwizard
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- gitsmart
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- logreader
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature could not get verified.
+- password_policy
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- provisioning_api
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- sketch
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- threatblock
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- two_factor_auth
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- user_ldap
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+- user_shibboleth
+ - EXCEPTION
+ - OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ - Signature data not found.
+
+Raw output
+==========
+Array
+(
+ [core] => Array
+ (
+ [EXTRA_FILE] => Array
+ (
+ [/testfile] => Array
+ (
+ )
+
+ )
+
+ [INVALID_HASH] => Array
+ (
+ [/.idea/workspace.xml] => Array
+ (
+ [expected] => f1c5e2630d784bc9cb02d5a28f55d6f24d06dae2a0fee685f3c2521b050955d9d452769f61454c9ddfa9c308146ade10546cfa829794448eaffbc9a04a29d216
+ [current] => ce08bf30bcbb879a18b49239a9bec6b8702f52452f88a9d32142cad8d2494d5735e6bfa0d8642b2762c62ca5be49f9bf4ec231d4a230559d4f3e2c471d3ea094
+ )
+
+ [/lib/private/integritycheck/checker.php] => Array
+ (
+ [expected] => c5a03bacae8dedf8b239997901ba1fffd2fe51271d13a00cc4b34b09cca5176397a89fc27381cbb1f72855fa18b69b6f87d7d5685c3b45aee373b09be54742ea
+ [current] => 88a3a92c11db91dec1ac3be0e1c87f862c95ba6ffaaaa3f2c3b8f682187c66f07af3a3b557a868342ef4a271218fe1c1e300c478e6c156c5955ed53c40d06585
+ )
+
+ [/settings/controller/checksetupcontroller.php] => Array
+ (
+ [expected] => 3e1de26ce93c7bfe0ede7c19cb6c93cadc010340225b375607a7178812e9de163179b0dc33809f451e01f491d93f6f5aaca7929685d21594cccf8bda732327c4
+ [current] => 09563164f9904a837f9ca0b5f626db56c838e5098e0ccc1d8b935f68fa03a25c5ec6f6b2d9e44a868e8b85764dafd1605522b4af8db0ae269d73432e9a01e63a
+ )
+
+ )
+
+ )
+
+ [bookmarks] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [dav] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [encryption] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [external] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [federation] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_antivirus] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_drop] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_external] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_pdfviewer] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_sharing] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_trashbin] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_versions] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [files_videoviewer] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [firstrunwizard] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [gitsmart] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [logreader] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature could not get verified.
+ )
+
+ )
+
+ [password_policy] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [provisioning_api] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [sketch] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [threatblock] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [two_factor_auth] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [user_ldap] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+ [user_shibboleth] => Array
+ (
+ [EXCEPTION] => Array
+ (
+ [class] => OC\IntegrityCheck\Exceptions\InvalidSignatureException
+ [message] => Signature data not found.
+ )
+
+ )
+
+)
+',
+ Http::STATUS_OK,
+ [
+ 'Content-Type' => 'text/plain',
+ ]
+ );
+ $this->assertEquals($expected, $this->checkSetupController->getFailedIntegrityCheckFiles());
+ }
+}
diff --git a/apps/settings/tests/Controller/DelegationControllerTest.php b/apps/settings/tests/Controller/DelegationControllerTest.php
new file mode 100644
index 00000000000..c4cbe67466b
--- /dev/null
+++ b/apps/settings/tests/Controller/DelegationControllerTest.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\Controller\Admin;
+
+use OC\Settings\AuthorizedGroup;
+use OCA\Settings\Controller\AuthorizedGroupController;
+use OCA\Settings\Service\AuthorizedGroupService;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class DelegationControllerTest extends TestCase {
+ private AuthorizedGroupService&MockObject $service;
+ private IRequest&MockObject $request;
+ private AuthorizedGroupController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->request = $this->createMock(IRequest::class);
+ $this->service = $this->createMock(AuthorizedGroupService::class);
+ $this->controller = new AuthorizedGroupController(
+ 'settings', $this->request, $this->service
+ );
+ }
+
+ public function testSaveSettings(): void {
+ $setting = 'MySecretSetting';
+ $oldGroups = [];
+ $oldGroups[] = AuthorizedGroup::fromParams(['groupId' => 'hello', 'class' => $setting]);
+ $goodbye = AuthorizedGroup::fromParams(['groupId' => 'goodbye', 'class' => $setting, 'id' => 42]);
+ $oldGroups[] = $goodbye;
+ $this->service->expects($this->once())
+ ->method('findExistingGroupsForClass')
+ ->with('MySecretSetting')
+ ->willReturn($oldGroups);
+
+ $this->service->expects($this->once())
+ ->method('delete')
+ ->with(42);
+
+ $this->service->expects($this->once())
+ ->method('create')
+ ->with('world', 'MySecretSetting')
+ ->willReturn(AuthorizedGroup::fromParams(['groupId' => 'world', 'class' => $setting]));
+
+ $result = $this->controller->saveSettings([['gid' => 'hello'], ['gid' => 'world']], 'MySecretSetting');
+
+ $this->assertEquals(['valid' => true], $result->getData());
+ }
+}
diff --git a/apps/settings/tests/Controller/MailSettingsControllerTest.php b/apps/settings/tests/Controller/MailSettingsControllerTest.php
new file mode 100644
index 00000000000..1bc05ca4718
--- /dev/null
+++ b/apps/settings/tests/Controller/MailSettingsControllerTest.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OC\Mail\Message;
+use OC\User\User;
+use OCA\Settings\Controller\MailSettingsController;
+use OCP\AppFramework\Http;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+use OCP\Mail\IEMailTemplate;
+use OCP\Mail\IMailer;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * @package Tests\Settings\Controller
+ */
+class MailSettingsControllerTest extends \Test\TestCase {
+ private IConfig&MockObject $config;
+ private IUserSession&MockObject $userSession;
+ private IMailer&MockObject $mailer;
+ private IL10N&MockObject $l;
+ private IURLGenerator&MockObject $urlGenerator;
+ private MailSettingsController $mailController;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l = $this->createMock(IL10N::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->mailer = $this->createMock(IMailer::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ /** @var IRequest&MockObject $request */
+ $request = $this->createMock(IRequest::class);
+ $this->mailController = new MailSettingsController(
+ 'settings',
+ $request,
+ $this->l,
+ $this->config,
+ $this->userSession,
+ $this->urlGenerator,
+ $this->mailer,
+ );
+ }
+
+ public function testSetMailSettings(): void {
+ $calls = [
+ [[
+ 'mail_domain' => 'nextcloud.com',
+ 'mail_from_address' => 'demo@nextcloud.com',
+ 'mail_smtpmode' => 'smtp',
+ 'mail_smtpsecure' => 'ssl',
+ 'mail_smtphost' => 'mx.nextcloud.org',
+ 'mail_smtpauth' => 1,
+ 'mail_smtpport' => '25',
+ 'mail_sendmailmode' => 'smtp',
+ ]],
+ [[
+ 'mail_domain' => 'nextcloud.com',
+ 'mail_from_address' => 'demo@nextcloud.com',
+ 'mail_smtpmode' => 'smtp',
+ 'mail_smtpsecure' => 'ssl',
+ 'mail_smtphost' => 'mx.nextcloud.org',
+ 'mail_smtpauth' => null,
+ 'mail_smtpport' => '25',
+ 'mail_smtpname' => null,
+ 'mail_smtppassword' => null,
+ 'mail_sendmailmode' => 'smtp',
+ ]],
+ ];
+ $this->config->expects($this->exactly(2))
+ ->method('setSystemValues')
+ ->willReturnCallback(function () use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ });
+
+ // With authentication
+ $response = $this->mailController->setMailSettings(
+ 'nextcloud.com',
+ 'demo@nextcloud.com',
+ 'smtp',
+ 'ssl',
+ 'mx.nextcloud.org',
+ '1',
+ '25',
+ 'smtp'
+ );
+ $this->assertSame(Http::STATUS_OK, $response->getStatus());
+
+ // Without authentication (testing the deletion of the stored password)
+ $response = $this->mailController->setMailSettings(
+ 'nextcloud.com',
+ 'demo@nextcloud.com',
+ 'smtp',
+ 'ssl',
+ 'mx.nextcloud.org',
+ '0',
+ '25',
+ 'smtp'
+ );
+ $this->assertSame(Http::STATUS_OK, $response->getStatus());
+ }
+
+ public function testStoreCredentials(): void {
+ $this->config
+ ->expects($this->once())
+ ->method('setSystemValues')
+ ->with([
+ 'mail_smtpname' => 'UsernameToStore',
+ 'mail_smtppassword' => 'PasswordToStore',
+ ]);
+
+ $response = $this->mailController->storeCredentials('UsernameToStore', 'PasswordToStore');
+ $this->assertSame(Http::STATUS_OK, $response->getStatus());
+ }
+
+ public function testSendTestMail(): void {
+ $user = $this->createMock(User::class);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('Werner');
+ $user->expects($this->any())
+ ->method('getDisplayName')
+ ->willReturn('Werner Brösel');
+
+ $this->l->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+ $this->userSession
+ ->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+
+ // Ensure that it fails when no mail address has been specified
+ $response = $this->mailController->sendTestMail();
+ $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus());
+ $this->assertSame('You need to set your account email before being able to send test emails. Go to for that.', $response->getData());
+
+ // If no exception is thrown it should work
+ $this->config
+ ->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn('mail@example.invalid');
+ $this->mailer->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMock(Message::class));
+ $emailTemplate = $this->createMock(IEMailTemplate::class);
+ $this->mailer
+ ->expects($this->once())
+ ->method('createEMailTemplate')
+ ->willReturn($emailTemplate);
+ $response = $this->mailController->sendTestMail();
+ $this->assertSame(Http::STATUS_OK, $response->getStatus());
+ }
+}
diff --git a/apps/settings/tests/Controller/TwoFactorSettingsControllerTest.php b/apps/settings/tests/Controller/TwoFactorSettingsControllerTest.php
new file mode 100644
index 00000000000..9f8d53d4f9f
--- /dev/null
+++ b/apps/settings/tests/Controller/TwoFactorSettingsControllerTest.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OC\Authentication\TwoFactorAuth\EnforcementState;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
+use OCA\Settings\Controller\TwoFactorSettingsController;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class TwoFactorSettingsControllerTest extends TestCase {
+ private IRequest&MockObject $request;
+ private MandatoryTwoFactor&MockObject $mandatoryTwoFactor;
+ private TwoFactorSettingsController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class);
+
+ $this->controller = new TwoFactorSettingsController(
+ 'settings',
+ $this->request,
+ $this->mandatoryTwoFactor
+ );
+ }
+
+ public function testIndex(): void {
+ $state = new EnforcementState(true);
+ $this->mandatoryTwoFactor->expects($this->once())
+ ->method('getState')
+ ->willReturn($state);
+ $expected = new JSONResponse($state);
+
+ $resp = $this->controller->index();
+
+ $this->assertEquals($expected, $resp);
+ }
+
+ public function testUpdate(): void {
+ $state = new EnforcementState(true);
+ $this->mandatoryTwoFactor->expects($this->once())
+ ->method('setState')
+ ->with($this->equalTo(new EnforcementState(true)));
+ $this->mandatoryTwoFactor->expects($this->once())
+ ->method('getState')
+ ->willReturn($state);
+ $expected = new JSONResponse($state);
+
+ $resp = $this->controller->update(true);
+
+ $this->assertEquals($expected, $resp);
+ }
+}
diff --git a/apps/settings/tests/Controller/UsersControllerTest.php b/apps/settings/tests/Controller/UsersControllerTest.php
new file mode 100644
index 00000000000..1012557bfc4
--- /dev/null
+++ b/apps/settings/tests/Controller/UsersControllerTest.php
@@ -0,0 +1,996 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2014-2015 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\Controller;
+
+use OC\Accounts\AccountManager;
+use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
+use OC\ForbiddenException;
+use OC\Group\Manager;
+use OC\KnownUser\KnownUserService;
+use OC\User\Manager as UserManager;
+use OCA\Settings\Controller\UsersController;
+use OCP\Accounts\IAccount;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\IAccountProperty;
+use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\BackgroundJob\IJobList;
+use OCP\Encryption\IEncryptionModule;
+use OCP\Encryption\IManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+use OCP\Mail\IMailer;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * @group DB
+ *
+ * @package Tests\Settings\Controller
+ */
+class UsersControllerTest extends \Test\TestCase {
+ private IGroupManager&MockObject $groupManager;
+ private UserManager&MockObject $userManager;
+ private IUserSession&MockObject $userSession;
+ private IConfig&MockObject $config;
+ private IMailer&MockObject $mailer;
+ private IFactory&MockObject $l10nFactory;
+ private IAppManager&MockObject $appManager;
+ private IL10N&MockObject $l;
+ private AccountManager&MockObject $accountManager;
+ private IJobList&MockObject $jobList;
+ private \OC\Security\IdentityProof\Manager&MockObject $securityManager;
+ private IManager&MockObject $encryptionManager;
+ private KnownUserService&MockObject $knownUserService;
+ private IEncryptionModule&MockObject $encryptionModule;
+ private IEventDispatcher&MockObject $dispatcher;
+ private IInitialState&MockObject $initialState;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userManager = $this->createMock(UserManager::class);
+ $this->groupManager = $this->createMock(Manager::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->l = $this->createMock(IL10N::class);
+ $this->mailer = $this->createMock(IMailer::class);
+ $this->l10nFactory = $this->createMock(IFactory::class);
+ $this->appManager = $this->createMock(IAppManager::class);
+ $this->accountManager = $this->createMock(AccountManager::class);
+ $this->securityManager = $this->createMock(\OC\Security\IdentityProof\Manager::class);
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->encryptionManager = $this->createMock(IManager::class);
+ $this->knownUserService = $this->createMock(KnownUserService::class);
+ $this->dispatcher = $this->createMock(IEventDispatcher::class);
+ $this->initialState = $this->createMock(IInitialState::class);
+
+ $this->l->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+
+ $this->encryptionModule = $this->createMock(IEncryptionModule::class);
+ $this->encryptionManager->expects($this->any())->method('getEncryptionModules')
+ ->willReturn(['encryptionModule' => ['callback' => function () {
+ return $this->encryptionModule;
+ }]]);
+ }
+
+ /**
+ * @param bool $isAdmin
+ * @return UsersController|MockObject
+ */
+ protected function getController(bool $isAdmin = false, array $mockedMethods = []) {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->willReturn($isAdmin);
+
+ if (empty($mockedMethods)) {
+ return new UsersController(
+ 'settings',
+ $this->createMock(IRequest::class),
+ $this->userManager,
+ $this->groupManager,
+ $this->userSession,
+ $this->config,
+ $this->l,
+ $this->mailer,
+ $this->l10nFactory,
+ $this->appManager,
+ $this->accountManager,
+ $this->securityManager,
+ $this->jobList,
+ $this->encryptionManager,
+ $this->knownUserService,
+ $this->dispatcher,
+ $this->initialState,
+ );
+ } else {
+ return $this->getMockBuilder(UsersController::class)
+ ->setConstructorArgs(
+ [
+ 'settings',
+ $this->createMock(IRequest::class),
+ $this->userManager,
+ $this->groupManager,
+ $this->userSession,
+ $this->config,
+ $this->l,
+ $this->mailer,
+ $this->l10nFactory,
+ $this->appManager,
+ $this->accountManager,
+ $this->securityManager,
+ $this->jobList,
+ $this->encryptionManager,
+ $this->knownUserService,
+ $this->dispatcher,
+ $this->initialState,
+ ]
+ )
+ ->onlyMethods($mockedMethods)
+ ->getMock();
+ }
+ }
+
+ protected function buildPropertyMock(string $name, string $value, string $scope, string $verified = IAccountManager::VERIFIED): MockObject {
+ $property = $this->createMock(IAccountProperty::class);
+ $property->expects($this->any())
+ ->method('getName')
+ ->willReturn($name);
+ $property->expects($this->any())
+ ->method('getValue')
+ ->willReturn($value);
+ $property->expects($this->any())
+ ->method('getScope')
+ ->willReturn($scope);
+ $property->expects($this->any())
+ ->method('getVerified')
+ ->willReturn($verified);
+
+ return $property;
+ }
+
+ protected function getDefaultAccountMock(): MockObject {
+ $propertyMocks = [
+ IAccountManager::PROPERTY_DISPLAYNAME => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_DISPLAYNAME,
+ 'Default display name',
+ IAccountManager::SCOPE_FEDERATED,
+ ),
+ IAccountManager::PROPERTY_ADDRESS => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_ADDRESS,
+ 'Default address',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_WEBSITE => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'Default website',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_EMAIL => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_EMAIL,
+ 'Default email',
+ IAccountManager::SCOPE_FEDERATED,
+ ),
+ IAccountManager::PROPERTY_AVATAR => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_AVATAR,
+ '',
+ IAccountManager::SCOPE_FEDERATED,
+ ),
+ IAccountManager::PROPERTY_PHONE => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_PHONE,
+ 'Default phone',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_TWITTER => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_TWITTER,
+ 'Default twitter',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_BLUESKY => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_BLUESKY,
+ 'Default bluesky',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_FEDIVERSE => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_FEDIVERSE,
+ 'Default fediverse',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_BIRTHDATE => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_BIRTHDATE,
+ 'Default birthdate',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ IAccountManager::PROPERTY_PRONOUNS => $this->buildPropertyMock(
+ IAccountManager::PROPERTY_PRONOUNS,
+ 'Default pronouns',
+ IAccountManager::SCOPE_LOCAL,
+ ),
+ ];
+
+ $account = $this->createMock(IAccount::class);
+ $account->expects($this->any())
+ ->method('getProperty')
+ ->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
+ if (isset($propertyMocks[$propertyName])) {
+ return $propertyMocks[$propertyName];
+ }
+ throw new PropertyDoesNotExistException($propertyName);
+ });
+ $account->expects($this->any())
+ ->method('getProperties')
+ ->willReturn($propertyMocks);
+
+ return $account;
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettings')]
+ public function testSetUserSettings(string $email, bool $validEmail, int $expectedStatus): void {
+ $controller = $this->getController(false, ['saveUserSettings']);
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('johndoe');
+
+ $this->userSession->method('getUser')->willReturn($user);
+
+ if (!empty($email) && $validEmail) {
+ $this->mailer->expects($this->once())->method('validateMailAddress')
+ ->willReturn($validEmail);
+ }
+
+ $saveData = (!empty($email) && $validEmail) || empty($email);
+
+ if ($saveData) {
+ $this->accountManager->expects($this->once())
+ ->method('getAccount')
+ ->with($user)
+ ->willReturn($this->getDefaultAccountMock());
+
+ $controller->expects($this->once())
+ ->method('saveUserSettings');
+ } else {
+ $controller->expects($this->never())->method('saveUserSettings');
+ }
+
+ $result = $controller->setUserSettings(
+ AccountManager::SCOPE_FEDERATED,
+ 'displayName',
+ AccountManager::SCOPE_FEDERATED,
+ '47658468',
+ AccountManager::SCOPE_FEDERATED,
+ $email,
+ AccountManager::SCOPE_FEDERATED,
+ 'nextcloud.com',
+ AccountManager::SCOPE_FEDERATED,
+ 'street and city',
+ AccountManager::SCOPE_FEDERATED,
+ '@nextclouders',
+ AccountManager::SCOPE_FEDERATED,
+ '@nextclouders',
+ AccountManager::SCOPE_FEDERATED,
+ '2020-01-01',
+ AccountManager::SCOPE_FEDERATED,
+ 'they/them',
+ AccountManager::SCOPE_FEDERATED,
+ );
+
+ $this->assertSame($expectedStatus, $result->getStatus());
+ }
+
+ public static function dataTestSetUserSettings(): array {
+ return [
+ ['', true, Http::STATUS_OK],
+ ['', false, Http::STATUS_OK],
+ ['example.com', false, Http::STATUS_UNPROCESSABLE_ENTITY],
+ ['john@example.com', true, Http::STATUS_OK],
+ ];
+ }
+
+ public function testSetUserSettingsWhenUserDisplayNameChangeNotAllowed(): void {
+ $controller = $this->getController(false, ['saveUserSettings']);
+
+ $avatarScope = IAccountManager::SCOPE_PUBLISHED;
+ $displayName = 'Display name';
+ $displayNameScope = IAccountManager::SCOPE_PUBLISHED;
+ $phone = '47658468';
+ $phoneScope = IAccountManager::SCOPE_PUBLISHED;
+ $email = 'john@example.com';
+ $emailScope = IAccountManager::SCOPE_PUBLISHED;
+ $website = 'nextcloud.com';
+ $websiteScope = IAccountManager::SCOPE_PUBLISHED;
+ $address = 'street and city';
+ $addressScope = IAccountManager::SCOPE_PUBLISHED;
+ $twitter = '@nextclouders';
+ $twitterScope = IAccountManager::SCOPE_PUBLISHED;
+ $fediverse = '@nextclouders@floss.social';
+ $fediverseScope = IAccountManager::SCOPE_PUBLISHED;
+ $birtdate = '2020-01-01';
+ $birthdateScope = IAccountManager::SCOPE_PUBLISHED;
+ $pronouns = 'she/her';
+ $pronounsScope = IAccountManager::SCOPE_PUBLISHED;
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('johndoe');
+
+ $this->userSession->method('getUser')->willReturn($user);
+
+ /** @var MockObject|IAccount $userAccount */
+ $userAccount = $this->getDefaultAccountMock();
+ $this->accountManager->expects($this->once())
+ ->method('getAccount')
+ ->with($user)
+ ->willReturn($userAccount);
+
+ /** @var MockObject|IAccountProperty $avatarProperty */
+ $avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR);
+ $avatarProperty->expects($this->atLeastOnce())
+ ->method('setScope')
+ ->with($avatarScope)
+ ->willReturnSelf();
+
+ /** @var MockObject|IAccountProperty $avatarProperty */
+ $avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS);
+ $avatarProperty->expects($this->atLeastOnce())
+ ->method('setScope')
+ ->with($addressScope)
+ ->willReturnSelf();
+ $avatarProperty->expects($this->atLeastOnce())
+ ->method('setValue')
+ ->with($address)
+ ->willReturnSelf();
+
+ /** @var MockObject|IAccountProperty $emailProperty */
+ $emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL);
+ $emailProperty->expects($this->never())
+ ->method('setValue');
+ $emailProperty->expects($this->never())
+ ->method('setScope');
+
+ /** @var MockObject|IAccountProperty $emailProperty */
+ $emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
+ $emailProperty->expects($this->never())
+ ->method('setValue');
+ $emailProperty->expects($this->never())
+ ->method('setScope');
+
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('allow_user_to_change_display_name')
+ ->willReturn(false);
+
+ $this->appManager->expects($this->any())
+ ->method('isEnabledForUser')
+ ->with('federatedfilesharing')
+ ->willReturn(true);
+
+ $this->mailer->expects($this->once())->method('validateMailAddress')
+ ->willReturn(true);
+
+ $controller->expects($this->once())
+ ->method('saveUserSettings');
+
+ $controller->setUserSettings(
+ $avatarScope,
+ $displayName,
+ $displayNameScope,
+ $phone,
+ $phoneScope,
+ $email,
+ $emailScope,
+ $website,
+ $websiteScope,
+ $address,
+ $addressScope,
+ $twitter,
+ $twitterScope,
+ $fediverse,
+ $fediverseScope,
+ $birtdate,
+ $birthdateScope,
+ $pronouns,
+ $pronounsScope,
+ );
+ }
+
+ public function testSetUserSettingsWhenFederatedFilesharingNotEnabled(): void {
+ $controller = $this->getController(false, ['saveUserSettings']);
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('johndoe');
+
+ $this->userSession->method('getUser')->willReturn($user);
+
+ $defaultProperties = []; //$this->getDefaultAccountMock();
+
+ $userAccount = $this->getDefaultAccountMock();
+ $this->accountManager->expects($this->once())
+ ->method('getAccount')
+ ->with($user)
+ ->willReturn($userAccount);
+
+ $this->appManager->expects($this->any())
+ ->method('isEnabledForUser')
+ ->with('federatedfilesharing')
+ ->willReturn(false);
+
+ $avatarScope = IAccountManager::SCOPE_PUBLISHED;
+ $displayName = 'Display name';
+ $displayNameScope = IAccountManager::SCOPE_PUBLISHED;
+ $phone = '47658468';
+ $phoneScope = IAccountManager::SCOPE_PUBLISHED;
+ $email = 'john@example.com';
+ $emailScope = IAccountManager::SCOPE_PUBLISHED;
+ $website = 'nextcloud.com';
+ $websiteScope = IAccountManager::SCOPE_PUBLISHED;
+ $address = 'street and city';
+ $addressScope = IAccountManager::SCOPE_PUBLISHED;
+ $twitter = '@nextclouders';
+ $twitterScope = IAccountManager::SCOPE_PUBLISHED;
+ $bluesky = 'nextclouders.net';
+ $blueskyScope = IAccountManager::SCOPE_PUBLISHED;
+ $fediverse = '@nextclouders@floss.social';
+ $fediverseScope = IAccountManager::SCOPE_PUBLISHED;
+ $birthdate = '2020-01-01';
+ $birthdateScope = IAccountManager::SCOPE_PUBLISHED;
+ $pronouns = 'she/her';
+ $pronounsScope = IAccountManager::SCOPE_PUBLISHED;
+
+ // All settings are changed (in the past phone, website, address and
+ // twitter were not changed).
+ $expectedProperties = $defaultProperties;
+ $expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
+ $expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
+ $expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displayNameScope;
+ $expectedProperties[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
+ $expectedProperties[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
+ $expectedProperties[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
+ $expectedProperties[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
+ $expectedProperties[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
+ $expectedProperties[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
+ $expectedProperties[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
+ $expectedProperties[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
+ $expectedProperties[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
+ $expectedProperties[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
+ $expectedProperties[IAccountManager::PROPERTY_BLUESKY]['value'] = $bluesky;
+ $expectedProperties[IAccountManager::PROPERTY_BLUESKY]['scope'] = $blueskyScope;
+ $expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['value'] = $fediverse;
+ $expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['scope'] = $fediverseScope;
+ $expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['value'] = $birthdate;
+ $expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['scope'] = $birthdateScope;
+ $expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['value'] = $pronouns;
+ $expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['scope'] = $pronounsScope;
+
+ $this->mailer->expects($this->once())->method('validateMailAddress')
+ ->willReturn(true);
+
+ $controller->expects($this->once())
+ ->method('saveUserSettings')
+ ->with($userAccount);
+
+ $controller->setUserSettings(
+ $avatarScope,
+ $displayName,
+ $displayNameScope,
+ $phone,
+ $phoneScope,
+ $email,
+ $emailScope,
+ $website,
+ $websiteScope,
+ $address,
+ $addressScope,
+ $twitter,
+ $twitterScope,
+ $bluesky,
+ $blueskyScope,
+ $fediverse,
+ $fediverseScope,
+ $birthdate,
+ $birthdateScope,
+ $pronouns,
+ $pronounsScope,
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettingsSubset')]
+ public function testSetUserSettingsSubset(string $property, string $propertyValue): void {
+ $controller = $this->getController(false, ['saveUserSettings']);
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('johndoe');
+
+ $this->userSession->method('getUser')->willReturn($user);
+
+ /** @var IAccount&MockObject $userAccount */
+ $userAccount = $this->getDefaultAccountMock();
+
+ $this->accountManager->expects($this->once())
+ ->method('getAccount')
+ ->with($user)
+ ->willReturn($userAccount);
+
+ $avatarScope = ($property === 'avatarScope') ? $propertyValue : null;
+ $displayName = ($property === 'displayName') ? $propertyValue : null;
+ $displayNameScope = ($property === 'displayNameScope') ? $propertyValue : null;
+ $phone = ($property === 'phone') ? $propertyValue : null;
+ $phoneScope = ($property === 'phoneScope') ? $propertyValue : null;
+ $email = ($property === 'email') ? $propertyValue : null;
+ $emailScope = ($property === 'emailScope') ? $propertyValue : null;
+ $website = ($property === 'website') ? $propertyValue : null;
+ $websiteScope = ($property === 'websiteScope') ? $propertyValue : null;
+ $address = ($property === 'address') ? $propertyValue : null;
+ $addressScope = ($property === 'addressScope') ? $propertyValue : null;
+ $twitter = ($property === 'twitter') ? $propertyValue : null;
+ $twitterScope = ($property === 'twitterScope') ? $propertyValue : null;
+ $bluesky = ($property === 'bluesky') ? $propertyValue : null;
+ $blueskyScope = ($property === 'blueskyScope') ? $propertyValue : null;
+ $fediverse = ($property === 'fediverse') ? $propertyValue : null;
+ $fediverseScope = ($property === 'fediverseScope') ? $propertyValue : null;
+ $birthdate = ($property === 'birthdate') ? $propertyValue : null;
+ $birthdateScope = ($property === 'birthdateScope') ? $propertyValue : null;
+ $pronouns = ($property === 'pronouns') ? $propertyValue : null;
+ $pronounsScope = ($property === 'pronounsScope') ? $propertyValue : null;
+
+ /** @var IAccountProperty[]&MockObject[] $expectedProperties */
+ $expectedProperties = $userAccount->getProperties();
+ $isScope = strrpos($property, 'Scope') === strlen($property) - strlen('5');
+ switch ($property) {
+ case 'avatarScope':
+ $propertyId = IAccountManager::PROPERTY_AVATAR;
+ break;
+ case 'displayName':
+ case 'displayNameScope':
+ $propertyId = IAccountManager::PROPERTY_DISPLAYNAME;
+ break;
+ case 'phone':
+ case 'phoneScope':
+ $propertyId = IAccountManager::PROPERTY_PHONE;
+ break;
+ case 'email':
+ case 'emailScope':
+ $propertyId = IAccountManager::PROPERTY_EMAIL;
+ break;
+ case 'website':
+ case 'websiteScope':
+ $propertyId = IAccountManager::PROPERTY_WEBSITE;
+ break;
+ case 'address':
+ case 'addressScope':
+ $propertyId = IAccountManager::PROPERTY_ADDRESS;
+ break;
+ case 'twitter':
+ case 'twitterScope':
+ $propertyId = IAccountManager::PROPERTY_TWITTER;
+ break;
+ case 'bluesky':
+ case 'blueskyScope':
+ $propertyId = IAccountManager::PROPERTY_BLUESKY;
+ break;
+ case 'fediverse':
+ case 'fediverseScope':
+ $propertyId = IAccountManager::PROPERTY_FEDIVERSE;
+ break;
+ case 'birthdate':
+ case 'birthdateScope':
+ $propertyId = IAccountManager::PROPERTY_BIRTHDATE;
+ break;
+ case 'pronouns':
+ case 'pronounsScope':
+ $propertyId = IAccountManager::PROPERTY_PRONOUNS;
+ break;
+ default:
+ $propertyId = '404';
+ }
+ $expectedProperties[$propertyId]->expects($this->any())
+ ->method($isScope ? 'getScope' : 'getValue')
+ ->willReturn($propertyValue);
+
+ if (!empty($email)) {
+ $this->mailer->expects($this->once())->method('validateMailAddress')
+ ->willReturn(true);
+ }
+
+ $controller->expects($this->once())
+ ->method('saveUserSettings')
+ ->with($userAccount);
+
+ $controller->setUserSettings(
+ $avatarScope,
+ $displayName,
+ $displayNameScope,
+ $phone,
+ $phoneScope,
+ $email,
+ $emailScope,
+ $website,
+ $websiteScope,
+ $address,
+ $addressScope,
+ $twitter,
+ $twitterScope,
+ $bluesky,
+ $blueskyScope,
+ $fediverse,
+ $fediverseScope,
+ $birthdate,
+ $birthdateScope,
+ $pronouns,
+ $pronounsScope,
+ );
+ }
+
+ public static function dataTestSetUserSettingsSubset(): array {
+ return [
+ ['avatarScope', IAccountManager::SCOPE_PUBLISHED],
+ ['displayName', 'Display name'],
+ ['displayNameScope', IAccountManager::SCOPE_PUBLISHED],
+ ['phone', '47658468'],
+ ['phoneScope', IAccountManager::SCOPE_PUBLISHED],
+ ['email', 'john@example.com'],
+ ['emailScope', IAccountManager::SCOPE_PUBLISHED],
+ ['website', 'nextcloud.com'],
+ ['websiteScope', IAccountManager::SCOPE_PUBLISHED],
+ ['address', 'street and city'],
+ ['addressScope', IAccountManager::SCOPE_PUBLISHED],
+ ['twitter', '@nextclouders'],
+ ['twitterScope', IAccountManager::SCOPE_PUBLISHED],
+ ['bluesky', 'nextclouders.net'],
+ ['blueskyScope', IAccountManager::SCOPE_PUBLISHED],
+ ['fediverse', '@nextclouders@floss.social'],
+ ['fediverseScope', IAccountManager::SCOPE_PUBLISHED],
+ ['birthdate', '2020-01-01'],
+ ['birthdateScope', IAccountManager::SCOPE_PUBLISHED],
+ ['pronouns', 'he/him'],
+ ['pronounsScope', IAccountManager::SCOPE_PUBLISHED],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettings')]
+ public function testSaveUserSettings(array $data, ?string $oldEmailAddress, ?string $oldDisplayName): void {
+ $controller = $this->getController();
+ $user = $this->createMock(IUser::class);
+
+ $user->method('getDisplayName')->willReturn($oldDisplayName);
+ $user->method('getSystemEMailAddress')->willReturn($oldEmailAddress);
+ $user->method('canChangeDisplayName')->willReturn(true);
+
+ if (strtolower($data[IAccountManager::PROPERTY_EMAIL]['value']) === strtolower($oldEmailAddress ?? '')) {
+ $user->expects($this->never())->method('setSystemEMailAddress');
+ } else {
+ $user->expects($this->once())->method('setSystemEMailAddress')
+ ->with($data[IAccountManager::PROPERTY_EMAIL]['value']);
+ }
+
+ if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ?? '') {
+ $user->expects($this->never())->method('setDisplayName');
+ } else {
+ $user->expects($this->once())->method('setDisplayName')
+ ->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
+ ->willReturn(true);
+ }
+
+ $properties = [];
+ foreach ($data as $propertyName => $propertyData) {
+ $properties[$propertyName] = $this->createMock(IAccountProperty::class);
+ $properties[$propertyName]->expects($this->any())
+ ->method('getValue')
+ ->willReturn($propertyData['value']);
+ }
+
+ $account = $this->createMock(IAccount::class);
+ $account->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $account->expects($this->any())
+ ->method('getProperty')
+ ->willReturnCallback(function (string $propertyName) use ($properties) {
+ return $properties[$propertyName];
+ });
+
+ $this->accountManager->expects($this->any())
+ ->method('getAccount')
+ ->willReturn($account);
+
+ $this->accountManager->expects($this->once())
+ ->method('updateAccount')
+ ->with($account);
+
+ $this->invokePrivate($controller, 'saveUserSettings', [$account]);
+ }
+
+ public static function dataTestSaveUserSettings(): array {
+ return [
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'john@example.com',
+ 'john doe'
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'johnNew@example.com',
+ 'john New doe'
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'johnNew@example.com',
+ 'john doe'
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'john@example.com',
+ 'john New doe'
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => ''],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ null,
+ 'john New doe'
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'john@example.com',
+ null
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'JOHN@example.com',
+ null
+ ],
+
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettingsException')]
+ public function testSaveUserSettingsException(
+ array $data,
+ string $oldEmailAddress,
+ string $oldDisplayName,
+ bool $setDisplayNameResult,
+ bool $canChangeEmail,
+ ): void {
+ $this->expectException(ForbiddenException::class);
+
+ $controller = $this->getController();
+ $user = $this->createMock(IUser::class);
+
+ $user->method('getDisplayName')->willReturn($oldDisplayName);
+ $user->method('getEMailAddress')->willReturn($oldEmailAddress);
+
+ /** @var MockObject|IAccount $userAccount */
+ $userAccount = $this->createMock(IAccount::class);
+ $userAccount->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $propertyMocks = [];
+ foreach ($data as $propertyName => $propertyData) {
+ /** @var MockObject|IAccountProperty $property */
+ $propertyMocks[$propertyName] = $this->buildPropertyMock($propertyName, $propertyData['value'], '');
+ }
+ $userAccount->expects($this->any())
+ ->method('getProperty')
+ ->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
+ return $propertyMocks[$propertyName];
+ });
+
+ if ($data[IAccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
+ $user->method('canChangeDisplayName')
+ ->willReturn($canChangeEmail);
+ }
+
+ if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
+ $user->method('setDisplayName')
+ ->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
+ ->willReturn($setDisplayNameResult);
+ }
+
+ $this->invokePrivate($controller, 'saveUserSettings', [$userAccount]);
+ }
+
+
+ public static function dataTestSaveUserSettingsException(): array {
+ return [
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'johnNew@example.com',
+ 'john New doe',
+ true,
+ false
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'johnNew@example.com',
+ 'john New doe',
+ false,
+ true
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
+ ],
+ 'johnNew@example.com',
+ 'john New doe',
+ false,
+ false
+ ],
+
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetVerificationCode')]
+ public function testGetVerificationCode(string $account, string $type, array $dataBefore, array $expectedData, bool $onlyVerificationCode): void {
+ $message = 'Use my Federated Cloud ID to share with me: user@nextcloud.com';
+ $signature = 'theSignature';
+
+ $code = $message . ' ' . $signature;
+ if ($type === IAccountManager::PROPERTY_TWITTER || $type === IAccountManager::PROPERTY_FEDIVERSE) {
+ $code = $message . ' ' . md5($signature);
+ }
+
+ $controller = $this->getController(false, ['signMessage', 'getCurrentTime']);
+
+ $user = $this->createMock(IUser::class);
+
+ $property = $this->buildPropertyMock($type, $dataBefore[$type]['value'], '', IAccountManager::NOT_VERIFIED);
+ $property->expects($this->atLeastOnce())
+ ->method('setVerified')
+ ->with(IAccountManager::VERIFICATION_IN_PROGRESS)
+ ->willReturnSelf();
+ $property->expects($this->atLeastOnce())
+ ->method('setVerificationData')
+ ->with($signature)
+ ->willReturnSelf();
+
+ $userAccount = $this->createMock(IAccount::class);
+ $userAccount->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $userAccount->expects($this->any())
+ ->method('getProperty')
+ ->willReturn($property);
+
+ $this->userSession->expects($this->once())->method('getUser')->willReturn($user);
+ $this->accountManager->expects($this->once())->method('getAccount')->with($user)->willReturn($userAccount);
+ $user->expects($this->any())->method('getCloudId')->willReturn('user@nextcloud.com');
+ $user->expects($this->any())->method('getUID')->willReturn('uid');
+ $controller->expects($this->once())->method('signMessage')->with($user, $message)->willReturn($signature);
+ $controller->expects($this->any())->method('getCurrentTime')->willReturn(1234567);
+
+ if ($onlyVerificationCode === false) {
+ $this->accountManager->expects($this->once())->method('updateAccount')->with($userAccount)->willReturnArgument(1);
+ $this->jobList->expects($this->once())->method('add')
+ ->with('OCA\Settings\BackgroundJobs\VerifyUserData',
+ [
+ 'verificationCode' => $code,
+ 'data' => $dataBefore[$type]['value'],
+ 'type' => $type,
+ 'uid' => 'uid',
+ 'try' => 0,
+ 'lastRun' => 1234567
+ ]);
+ }
+
+ $result = $controller->getVerificationCode($account, $onlyVerificationCode);
+
+ $data = $result->getData();
+ $this->assertSame(Http::STATUS_OK, $result->getStatus());
+ $this->assertSame($code, $data['code']);
+ }
+
+ public static function dataTestGetVerificationCode(): array {
+ $accountDataBefore = [
+ IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
+ IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
+ ];
+
+ $accountDataAfterWebsite = [
+ IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
+ IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
+ ];
+
+ $accountDataAfterTwitter = [
+ IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
+ IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
+ ];
+
+ return [
+ ['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
+ ['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
+ ['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
+ ['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
+ ];
+ }
+
+ /**
+ * test get verification code in case no valid user was given
+ */
+ public function testGetVerificationCodeInvalidUser(): void {
+ $controller = $this->getController();
+ $this->userSession->expects($this->once())->method('getUser')->willReturn(null);
+ $result = $controller->getVerificationCode('account', false);
+
+ $this->assertSame(Http::STATUS_BAD_REQUEST, $result->getStatus());
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCanAdminChangeUserPasswords')]
+ public function testCanAdminChangeUserPasswords(
+ bool $encryptionEnabled,
+ bool $encryptionModuleLoaded,
+ bool $masterKeyEnabled,
+ bool $expected,
+ ): void {
+ $controller = $this->getController();
+
+ $this->encryptionManager->expects($this->any())
+ ->method('isEnabled')
+ ->willReturn($encryptionEnabled);
+ $this->encryptionManager->expects($this->any())
+ ->method('getEncryptionModule')
+ ->willReturnCallback(function () use ($encryptionModuleLoaded) {
+ if ($encryptionModuleLoaded) {
+ return $this->encryptionModule;
+ } else {
+ throw new ModuleDoesNotExistsException();
+ }
+ });
+ $this->encryptionModule->expects($this->any())
+ ->method('needDetailedAccessList')
+ ->willReturn(!$masterKeyEnabled);
+
+ $result = $this->invokePrivate($controller, 'canAdminChangeUserPasswords', []);
+ $this->assertSame($expected, $result);
+ }
+
+ public static function dataTestCanAdminChangeUserPasswords(): array {
+ return [
+ // encryptionEnabled, encryptionModuleLoaded, masterKeyEnabled, expectedResult
+ [true, true, true, true],
+ [false, true, true, true],
+ [true, false, true, false],
+ [false, false, true, true],
+ [true, true, false, false],
+ [false, true, false, false],
+ [true, false, false, false],
+ [false, false, false, true],
+ ];
+ }
+}