diff options
Diffstat (limited to 'apps/settings/tests/Controller')
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], + ]; + } +} |