From d26ec02aa1fa1a0f423cce18d2138951b6b24c33 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Tue, 13 Jan 2026 18:39:21 +0100 Subject: [PATCH] refactor(core): migrate login flow ui from jQuery to Vue Signed-off-by: Ferdinand Thiessen --- build/frontend-legacy/webpack.modules.cjs | 1 + core/Controller/ClientFlowLoginController.php | 83 ++++++++------ .../ClientFlowLoginV2Controller.php | 62 +++++----- core/js/login/authpicker.js | 19 ---- core/js/login/grant.js | 32 ------ .../LoginFlow/LoginFlowAuthAppToken.vue | 49 ++++++++ .../LoginFlow/LoginFlowContainer.vue | 26 +++++ core/src/login-flow.ts | 22 ++++ core/src/views/LoginFlowAuth.vue | 73 ++++++++++++ core/src/views/LoginFlowDone.vue | 20 ++++ core/src/views/LoginFlowGrant.vue | 106 ++++++++++++++++++ core/templates/loginflow.php | 8 ++ core/templates/loginflow/authpicker.php | 59 ---------- core/templates/loginflow/grant.php | 47 -------- core/templates/loginflowv2/authpicker.php | 56 --------- core/templates/loginflowv2/done.php | 21 ---- core/templates/loginflowv2/grant.php | 44 -------- .../ClientFlowLoginControllerTest.php | 88 ++++++++++----- .../ClientFlowLoginV2ControllerTest.php | 30 +++-- 19 files changed, 463 insertions(+), 383 deletions(-) delete mode 100644 core/js/login/authpicker.js delete mode 100644 core/js/login/grant.js create mode 100644 core/src/components/LoginFlow/LoginFlowAuthAppToken.vue create mode 100644 core/src/components/LoginFlow/LoginFlowContainer.vue create mode 100644 core/src/login-flow.ts create mode 100644 core/src/views/LoginFlowAuth.vue create mode 100644 core/src/views/LoginFlowDone.vue create mode 100644 core/src/views/LoginFlowGrant.vue create mode 100644 core/templates/loginflow.php delete mode 100644 core/templates/loginflow/authpicker.php delete mode 100644 core/templates/loginflow/grant.php delete mode 100644 core/templates/loginflowv2/authpicker.php delete mode 100644 core/templates/loginflowv2/done.php delete mode 100644 core/templates/loginflowv2/grant.php diff --git a/build/frontend-legacy/webpack.modules.cjs b/build/frontend-legacy/webpack.modules.cjs index 05384ed27f7..b7e557f3dad 100644 --- a/build/frontend-legacy/webpack.modules.cjs +++ b/build/frontend-legacy/webpack.modules.cjs @@ -16,6 +16,7 @@ module.exports = { files_fileinfo: path.join(__dirname, 'core/src', 'files/fileinfo.js'), install: path.join(__dirname, 'core/src', 'install.ts'), login: path.join(__dirname, 'core/src', 'login.js'), + login_flow: path.join(__dirname, 'core/src', 'login-flow.ts'), main: path.join(__dirname, 'core/src', 'main.js'), maintenance: path.join(__dirname, 'core/src', 'maintenance.js'), 'public-page-menu': path.resolve(__dirname, 'core/src', 'public-page-menu.ts'), diff --git a/core/Controller/ClientFlowLoginController.php b/core/Controller/ClientFlowLoginController.php index 4464af890c4..0e83ef283a9 100644 --- a/core/Controller/ClientFlowLoginController.php +++ b/core/Controller/ClientFlowLoginController.php @@ -25,6 +25,7 @@ use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Authentication\Token\IToken; @@ -35,11 +36,11 @@ use OCP\IL10N; use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; -use OCP\IUser; use OCP\IUserSession; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; use OCP\Session\Exceptions\SessionNotAvailableException; +use OCP\Util; #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class ClientFlowLoginController extends Controller { @@ -61,6 +62,7 @@ class ClientFlowLoginController extends Controller { private IEventDispatcher $eventDispatcher, private ITimeFactory $timeFactory, private IConfig $config, + private IInitialState $initialState, ) { parent::__construct($appName, $request); } @@ -135,24 +137,36 @@ class ClientFlowLoginController extends Controller { $csp->addAllowedFormActionDomain('nc://*'); } + $this->initialState->provideInitialState('loginFlowState', 'auth'); + $this->initialState->provideInitialState('loginFlowAuth', [ + 'client' => $clientName, + 'clientIdentifier' => $clientIdentifier, + 'instanceName' => $this->defaults->getName(), + 'stateToken' => $stateToken, + 'serverHost' => $this->getServerPath(), + 'oauthState' => $this->session->get('oauth.state'), + 'direct' => (bool)$direct, + 'providedRedirectUri' => $providedRedirectUri, + 'loginRedirectUrl' => $this->urlGenerator->linkToRoute( + 'core.ClientFlowLogin.grantPage', + [ + 'stateToken' => $stateToken, + 'clientIdentifier' => $clientIdentifier, + 'oauthState' => $this->session->get('oauth.state'), + 'user' => $user, + 'direct' => $direct, + 'providedRedirectUri' => $providedRedirectUri, + ]), + 'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.apptokenRedirect'), + ]); + + + Util::addScript('core', 'login_flow'); $response = new StandaloneTemplateResponse( $this->appName, - 'loginflow/authpicker', - [ - 'client' => $clientName, - 'clientIdentifier' => $clientIdentifier, - 'instanceName' => $this->defaults->getName(), - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => $stateToken, - 'serverHost' => $this->getServerPath(), - 'oauthState' => $this->session->get('oauth.state'), - 'user' => $user, - 'direct' => $direct, - 'providedRedirectUri' => $providedRedirectUri, - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); - $response->setContentSecurityPolicy($csp); return $response; } @@ -188,26 +202,31 @@ class ClientFlowLoginController extends Controller { $csp->addAllowedFormActionDomain('nc://*'); } - /** @var IUser $user */ $user = $this->userSession->getUser(); + \assert($user !== null); + $this->initialState->provideInitialState('loginFlowState', 'grant'); + $this->initialState->provideInitialState('loginFlowGrant', [ + 'actionUrl' => $this->urlGenerator->linkToRouteAbsolute( + 'core.ClientFlowLogin.generateAppPassword', + ), + 'client' => $clientName, + 'clientIdentifier' => $clientIdentifier, + 'instanceName' => $this->defaults->getName(), + 'stateToken' => $stateToken, + 'serverHost' => $this->getServerPath(), + 'oauthState' => $this->session->get('oauth.state'), + 'direct' => $direct, + 'providedRedirectUri' => $providedRedirectUri, + 'userDisplayName' => $user->getDisplayName(), + 'userId' => $user->getUID(), + ]); + + Util::addScript('core', 'login_flow'); $response = new StandaloneTemplateResponse( $this->appName, - 'loginflow/grant', - [ - 'userId' => $user->getUID(), - 'userDisplayName' => $user->getDisplayName(), - 'client' => $clientName, - 'clientIdentifier' => $clientIdentifier, - 'instanceName' => $this->defaults->getName(), - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => $stateToken, - 'serverHost' => $this->getServerPath(), - 'oauthState' => $this->session->get('oauth.state'), - 'direct' => $direct, - 'providedRedirectUri' => $providedRedirectUri, - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); $response->setContentSecurityPolicy($csp); diff --git a/core/Controller/ClientFlowLoginV2Controller.php b/core/Controller/ClientFlowLoginV2Controller.php index 8c0c1e8179d..9a1694a93be 100644 --- a/core/Controller/ClientFlowLoginV2Controller.php +++ b/core/Controller/ClientFlowLoginV2Controller.php @@ -26,16 +26,17 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Defaults; use OCP\IL10N; use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; -use OCP\IUser; use OCP\IUserSession; use OCP\Security\ISecureRandom; use OCP\Server; +use OCP\Util; /** * @psalm-import-type CoreLoginFlowV2Credentials from ResponseDefinitions @@ -58,6 +59,7 @@ class ClientFlowLoginV2Controller extends Controller { private Defaults $defaults, private ?string $userId, private IL10N $l10n, + private IInitialState $initialState, ) { parent::__construct($appName, $request); } @@ -122,18 +124,21 @@ class ClientFlowLoginV2Controller extends Controller { ); $this->session->set(self::STATE_NAME, $stateToken); + $this->initialState->provideInitialState('loginFlowState', 'auth'); + $this->initialState->provideInitialState('loginFlowAuth', [ + 'client' => $flow->getClientName(), + 'instanceName' => $this->defaults->getName(), + 'stateToken' => $stateToken, + 'loginRedirectUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.grantPage', ['stateToken' => $stateToken, 'user' => $user, 'direct' => $direct]), + 'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.apptokenRedirect'), + ]); + + + Util::addScript('core', 'login_flow'); return new StandaloneTemplateResponse( $this->appName, - 'loginflowv2/authpicker', - [ - 'client' => $flow->getClientName(), - 'instanceName' => $this->defaults->getName(), - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => $stateToken, - 'user' => $user, - 'direct' => $direct, - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); } @@ -161,22 +166,26 @@ class ClientFlowLoginV2Controller extends Controller { return $this->loginTokenForbiddenClientResponse(); } - /** @var IUser $user */ $user = $this->userSession->getUser(); + \assert($user !== null); + $this->initialState->provideInitialState('loginFlowState', 'grant'); + $this->initialState->provideInitialState('loginFlowGrant', [ + 'actionUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.generateAppPassword'), + 'userId' => $user->getUID(), + 'userDisplayName' => $user->getDisplayName(), + 'client' => $flow->getClientName(), + 'instanceName' => $this->defaults->getName(), + 'stateToken' => $stateToken, + 'direct' => $direct === 1, + ]); + + + Util::addScript('core', 'login_flow'); return new StandaloneTemplateResponse( $this->appName, - 'loginflowv2/grant', - [ - 'userId' => $user->getUID(), - 'userDisplayName' => $user->getDisplayName(), - 'client' => $flow->getClientName(), - 'instanceName' => $this->defaults->getName(), - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => $stateToken, - 'direct' => $direct, - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); } @@ -260,11 +269,12 @@ class ClientFlowLoginV2Controller extends Controller { private function handleFlowDone(bool $result): StandaloneTemplateResponse { if ($result) { + Util::addScript('core', 'login_flow'); + $this->initialState->provideInitialState('loginFlowState', 'done'); return new StandaloneTemplateResponse( $this->appName, - 'loginflowv2/done', - [], - 'guest' + 'loginflow', + renderAs: 'guest' ); } diff --git a/core/js/login/authpicker.js b/core/js/login/authpicker.js deleted file mode 100644 index 3c612400f1d..00000000000 --- a/core/js/login/authpicker.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -jQuery(document).ready(function() { - $('#app-token-login').click(function(e) { - e.preventDefault() - $(this).addClass('hidden') - $('#redirect-link').addClass('hidden') - $('#app-token-login-field').removeClass('hidden') - }) - - document.getElementById('login-form').addEventListener('submit', function(e) { - e.preventDefault() - document.location.href = e.target.attributes.action.value - }) - - $('#login-form input').removeAttr('disabled') -}) diff --git a/core/js/login/grant.js b/core/js/login/grant.js deleted file mode 100644 index c71ccbbee1b..00000000000 --- a/core/js/login/grant.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -const form = document.querySelector('form') -form.addEventListener('submit', function(event) { - const wrapper = document.getElementById('submit-wrapper') - if (wrapper === null) { - return - } - - if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { - // stop the event - event.preventDefault() - event.stopPropagation() - - // handle password confirmation - OC.PasswordConfirmation.requirePasswordConfirmation(function() { - // when password is confirmed we submit the form - form.submit() - }) - - return false - } - - Array.from(wrapper.getElementsByClassName('icon-confirm-white')).forEach(function(el) { - el.classList.remove('icon-confirm-white') - el.classList.add(OCA.Theming && OCA.Theming.inverted ? 'icon-loading-small' : 'icon-loading-small-dark') - el.disabled = true - }) -}) diff --git a/core/src/components/LoginFlow/LoginFlowAuthAppToken.vue b/core/src/components/LoginFlow/LoginFlowAuthAppToken.vue new file mode 100644 index 00000000000..37df3fcb22d --- /dev/null +++ b/core/src/components/LoginFlow/LoginFlowAuthAppToken.vue @@ -0,0 +1,49 @@ + + + + + + + diff --git a/core/src/components/LoginFlow/LoginFlowContainer.vue b/core/src/components/LoginFlow/LoginFlowContainer.vue new file mode 100644 index 00000000000..9f0b8e11a28 --- /dev/null +++ b/core/src/components/LoginFlow/LoginFlowContainer.vue @@ -0,0 +1,26 @@ + + + + + + + diff --git a/core/src/login-flow.ts b/core/src/login-flow.ts new file mode 100644 index 00000000000..955bdb55266 --- /dev/null +++ b/core/src/login-flow.ts @@ -0,0 +1,22 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getCSPNonce } from '@nextcloud/auth' +import { loadState } from '@nextcloud/initial-state' +import Vue, { defineAsyncComponent } from 'vue' + +__webpack_nonce__ = getCSPNonce() + +const LoginFlowAuth = defineAsyncComponent(() => import('./views/LoginFlowAuth.vue')) +const LoginFlowGrant = defineAsyncComponent(() => import('./views/LoginFlowGrant.vue')) +const LoginFlowDone = defineAsyncComponent(() => import('./views/LoginFlowDone.vue')) + +const state = loadState<'auth' | 'grant' | 'done'>('core', 'loginFlowState') +const app = new Vue({ + render: (h) => h(state === 'auth' + ? LoginFlowAuth + : (state === 'grant' ? LoginFlowGrant : LoginFlowDone)), +}) +app.$mount('#core-loginflow') diff --git a/core/src/views/LoginFlowAuth.vue b/core/src/views/LoginFlowAuth.vue new file mode 100644 index 00000000000..20c1ef2e44c --- /dev/null +++ b/core/src/views/LoginFlowAuth.vue @@ -0,0 +1,73 @@ + + + + + + + diff --git a/core/src/views/LoginFlowDone.vue b/core/src/views/LoginFlowDone.vue new file mode 100644 index 00000000000..e81476c19a7 --- /dev/null +++ b/core/src/views/LoginFlowDone.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/core/src/views/LoginFlowGrant.vue b/core/src/views/LoginFlowGrant.vue new file mode 100644 index 00000000000..b2230bf9da6 --- /dev/null +++ b/core/src/views/LoginFlowGrant.vue @@ -0,0 +1,106 @@ + + + + + + + diff --git a/core/templates/loginflow.php b/core/templates/loginflow.php new file mode 100644 index 00000000000..40178d69075 --- /dev/null +++ b/core/templates/loginflow.php @@ -0,0 +1,8 @@ + + +
\ No newline at end of file diff --git a/core/templates/loginflow/authpicker.php b/core/templates/loginflow/authpicker.php deleted file mode 100644 index 265cb04a20f..00000000000 --- a/core/templates/loginflow/authpicker.php +++ /dev/null @@ -1,59 +0,0 @@ - - -
-

t('Connect to your account')) ?>

-

- t('Please log in before granting %1$s access to your %2$s account.', [ - '' . \OCP\Util::sanitizeHTML($_['client']) . '', - \OCP\Util::sanitizeHTML($_['instanceName']) - ])) ?> -

- -
-

t('Security warning')) ?>

-

- t('If you are not trying to set up a new device or app, someone is trying to trick you into granting them access to your data. In this case do not proceed and instead contact your system administrator.')) ?> -

-
- -
- -
- -
-

- - - - - t('Alternative log in using app password')) ?> - -
diff --git a/core/templates/loginflow/grant.php b/core/templates/loginflow/grant.php deleted file mode 100644 index 8d092f8e005..00000000000 --- a/core/templates/loginflow/grant.php +++ /dev/null @@ -1,47 +0,0 @@ - - -
-

t('Account access')) ?>

-

- t('Currently logged in as %1$s (%2$s).', [ - $_['userDisplayName'], - $_['userId'], - ])) ?> -

-

- t('You are about to grant %1$s access to your %2$s account.', [ - '' . \OCP\Util::sanitizeHTML($_['client']) . '', - \OCP\Util::sanitizeHTML($_['instanceName']) - ])) ?> -

- -
- -
- - - - - - - - -
- -
-
-

-
diff --git a/core/templates/loginflowv2/authpicker.php b/core/templates/loginflowv2/authpicker.php deleted file mode 100644 index c60aa81d3ea..00000000000 --- a/core/templates/loginflowv2/authpicker.php +++ /dev/null @@ -1,56 +0,0 @@ - - -
-

t('Connect to your account')) ?>

-

- t('Please log in before granting %1$s access to your %2$s account.', [ - '' . \OCP\Util::sanitizeHTML($_['client']) . '', - \OCP\Util::sanitizeHTML($_['instanceName']) - ])) ?> -

- -
-

t('Security warning')) ?>

-

- t('If you are not trying to set up a new device or app, someone is trying to trick you into granting them access to your data. In this case do not proceed and instead contact your system administrator.')) ?> -

-
- -
- -
- -
-

- - - - - t('Alternative log in using app password')) ?> - -
diff --git a/core/templates/loginflowv2/done.php b/core/templates/loginflowv2/done.php deleted file mode 100644 index b0369a0a637..00000000000 --- a/core/templates/loginflowv2/done.php +++ /dev/null @@ -1,21 +0,0 @@ - - -
-

t('Account connected')) ?>

-

- t('Your client should now be connected!')) ?>
- t('You can close this window.')) ?> -

- -
-
diff --git a/core/templates/loginflowv2/grant.php b/core/templates/loginflowv2/grant.php deleted file mode 100644 index dea4ed27d6c..00000000000 --- a/core/templates/loginflowv2/grant.php +++ /dev/null @@ -1,44 +0,0 @@ - - -
-

t('Account access')) ?>

-

- t('Currently logged in as %1$s (%2$s).', [ - $_['userDisplayName'], - $_['userId'], - ])) ?> -

-

- t('You are about to grant %1$s access to your %2$s account.', [ - '' . \OCP\Util::sanitizeHTML($_['client']) . '', - \OCP\Util::sanitizeHTML($_['instanceName']) - ])) ?> -

- -
- -
- - - - - -
- -
-
-

-
diff --git a/tests/Core/Controller/ClientFlowLoginControllerTest.php b/tests/Core/Controller/ClientFlowLoginControllerTest.php index b182bb1bb39..7a5bbbec82d 100644 --- a/tests/Core/Controller/ClientFlowLoginControllerTest.php +++ b/tests/Core/Controller/ClientFlowLoginControllerTest.php @@ -22,6 +22,7 @@ use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; @@ -53,6 +54,7 @@ class ClientFlowLoginControllerTest extends TestCase { private IEventDispatcher&MockObject $eventDispatcher; private ITimeFactory&MockObject $timeFactory; private IConfig&MockObject $config; + private IInitialState&MockObject $initialState; private ClientFlowLoginController $clientFlowLoginController; @@ -79,6 +81,7 @@ class ClientFlowLoginControllerTest extends TestCase { $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->config = $this->createMock(IConfig::class); + $this->initialState = $this->createMock(IInitialState::class); $this->clientFlowLoginController = new ClientFlowLoginController( 'core', @@ -96,6 +99,7 @@ class ClientFlowLoginControllerTest extends TestCase { $this->eventDispatcher, $this->timeFactory, $this->config, + $this->initialState, ); } @@ -138,7 +142,7 @@ class ClientFlowLoginControllerTest extends TestCase { ->method('set') ->with('client.flow.state.token', 'StateToken'); $this->session - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('get') ->with('oauth.state') ->willReturn('OauthStateToken'); @@ -154,27 +158,39 @@ class ClientFlowLoginControllerTest extends TestCase { ->method('getServerProtocol') ->willReturn('https'); + $initialState = []; + $this->initialState->expects($this->exactly(2)) + ->method('provideInitialState') + ->willReturnCallback(function () use (&$initialState) { + $initialState[] = func_get_args(); + }); + $expected = new StandaloneTemplateResponse( 'core', - 'loginflow/authpicker', - [ - 'client' => 'Mac OS X Sync Client', - 'clientIdentifier' => '', - 'instanceName' => 'ExampleCloud', - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => 'StateToken', - 'serverHost' => 'https://example.com', - 'oauthState' => 'OauthStateToken', - 'user' => '', - 'direct' => 0, - 'providedRedirectUri' => '', - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); $csp = new ContentSecurityPolicy(); $csp->addAllowedFormActionDomain('nc://*'); $expected->setContentSecurityPolicy($csp); $this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage()); + self::assertEquals([ + ['loginFlowState', 'auth'], + [ + 'loginFlowAuth', [ + 'client' => 'Mac OS X Sync Client', + 'clientIdentifier' => '', + 'instanceName' => 'ExampleCloud', + 'stateToken' => 'StateToken', + 'serverHost' => 'https://example.com', + 'oauthState' => 'OauthStateToken', + 'direct' => false, + 'providedRedirectUri' => '', + 'appTokenUrl' => '', + 'loginRedirectUrl' => '', + ], + ], + ], $initialState); } public function testShowAuthPickerPageWithOauth(): void { @@ -205,7 +221,7 @@ class ClientFlowLoginControllerTest extends TestCase { ->method('set') ->with('client.flow.state.token', 'StateToken'); $this->session - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('get') ->with('oauth.state') ->willReturn('OauthStateToken'); @@ -221,27 +237,39 @@ class ClientFlowLoginControllerTest extends TestCase { ->method('getServerProtocol') ->willReturn('https'); + $initialState = []; + $this->initialState->expects($this->exactly(2)) + ->method('provideInitialState') + ->willReturnCallback(function () use (&$initialState) { + $initialState[] = func_get_args(); + }); + $expected = new StandaloneTemplateResponse( 'core', - 'loginflow/authpicker', - [ - 'client' => 'My external service', - 'clientIdentifier' => 'MyClientIdentifier', - 'instanceName' => 'ExampleCloud', - 'urlGenerator' => $this->urlGenerator, - 'stateToken' => 'StateToken', - 'serverHost' => 'https://example.com', - 'oauthState' => 'OauthStateToken', - 'user' => '', - 'direct' => 0, - 'providedRedirectUri' => '', - ], - 'guest' + 'loginflow', + renderAs: 'guest' ); $csp = new ContentSecurityPolicy(); $csp->addAllowedFormActionDomain('https://example.com/redirect.php'); $expected->setContentSecurityPolicy($csp); $this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage('MyClientIdentifier')); + self::assertEquals([ + ['loginFlowState', 'auth'], + [ + 'loginFlowAuth', [ + 'client' => 'My external service', + 'clientIdentifier' => 'MyClientIdentifier', + 'instanceName' => 'ExampleCloud', + 'stateToken' => 'StateToken', + 'serverHost' => 'https://example.com', + 'oauthState' => 'OauthStateToken', + 'direct' => false, + 'providedRedirectUri' => '', + 'appTokenUrl' => '', + 'loginRedirectUrl' => '', + ], + ], + ], $initialState); } public function testGenerateAppPasswordWithInvalidToken(): void { diff --git a/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php b/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php index d130eb75c1a..556869e25f9 100644 --- a/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php +++ b/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php @@ -17,6 +17,7 @@ use OC\Core\Service\LoginFlowV2Service; use OCP\AppFramework\Http; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\Defaults; use OCP\IL10N; use OCP\IRequest; @@ -29,22 +30,15 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ClientFlowLoginV2ControllerTest extends TestCase { - /** @var IRequest|MockObject */ - private $request; - /** @var LoginFlowV2Service|MockObject */ - private $loginFlowV2Service; - /** @var IURLGenerator|MockObject */ - private $urlGenerator; - /** @var ISession|MockObject */ - private $session; - /** @var IUserSession|MockObject */ - private $userSession; - /** @var ISecureRandom|MockObject */ - private $random; - /** @var Defaults|MockObject */ - private $defaults; - /** @var IL10N|MockObject */ - private $l; + private IRequest&MockObject $request; + private LoginFlowV2Service&MockObject $loginFlowV2Service; + private IURLGenerator&MockObject $urlGenerator; + private ISession&MockObject $session; + private IUserSession&MockObject $userSession; + private ISecureRandom&MockObject $random; + private Defaults&MockObject $defaults; + private IInitialState&MockObject $initialState; + private IL10N&MockObject $l; /** @var ClientFlowLoginV2Controller */ private $controller; @@ -58,6 +52,7 @@ class ClientFlowLoginV2ControllerTest extends TestCase { $this->userSession = $this->createMock(IUserSession::class); $this->random = $this->createMock(ISecureRandom::class); $this->defaults = $this->createMock(Defaults::class); + $this->initialState = $this->createMock(IInitialState::class); $this->l = $this->createMock(IL10N::class); $this->l ->expects($this->any()) @@ -75,7 +70,8 @@ class ClientFlowLoginV2ControllerTest extends TestCase { $this->random, $this->defaults, 'user', - $this->l + $this->l, + $this->initialState, ); }