mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
refactor(core): migrate login flow ui from jQuery to Vue
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
038d940df1
commit
d26ec02aa1
19 changed files with 463 additions and 383 deletions
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
|
@ -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
|
||||
})
|
||||
})
|
||||
49
core/src/components/LoginFlow/LoginFlowAuthAppToken.vue
Normal file
49
core/src/components/LoginFlow/LoginFlowAuthAppToken.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
|
||||
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
|
||||
defineProps<{
|
||||
appTokenUrl: string
|
||||
direct: boolean
|
||||
stateToken: string
|
||||
}>()
|
||||
|
||||
const requestToken = getRequestToken()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form :action="appTokenUrl" :class="$style.loginFlowAuthAppToken" method="post">
|
||||
<NcFormBox>
|
||||
<NcTextField name="user" :label="t('core', 'Login')" />
|
||||
<NcPasswordField name="password" :label="t('core', 'App password')" />
|
||||
</NcFormBox>
|
||||
<input type="hidden" name="stateToken" :value="stateToken">
|
||||
<input type="hidden" name="requesttoken" :value="requestToken">
|
||||
<input
|
||||
v-if="direct"
|
||||
type="hidden"
|
||||
name="direct"
|
||||
value="1">
|
||||
|
||||
<NcButton type="submit" variant="primary" wide>
|
||||
{{ t('core', 'Grant access') }}
|
||||
</NcButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.loginFlowAuthAppToken {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--default-grid-baseline);
|
||||
}
|
||||
</style>
|
||||
26
core/src/components/LoginFlow/LoginFlowContainer.vue
Normal file
26
core/src/components/LoginFlow/LoginFlowContainer.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
|
||||
|
||||
defineProps<{
|
||||
heading: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcGuestContent class="picker-window" :class="$style.loginFlowContainer">
|
||||
<h2>{{ heading }}</h2>
|
||||
<slot />
|
||||
</NcGuestContent>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.loginFlowContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
22
core/src/login-flow.ts
Normal file
22
core/src/login-flow.ts
Normal file
|
|
@ -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')
|
||||
73
core/src/views/LoginFlowAuth.vue
Normal file
73
core/src/views/LoginFlowAuth.vue
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { ref } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import LoginFlowAuthAppToken from '../components/LoginFlow/LoginFlowAuthAppToken.vue'
|
||||
import LoginFlowContainer from '../components/LoginFlow/LoginFlowContainer.vue'
|
||||
|
||||
const {
|
||||
client,
|
||||
direct,
|
||||
instanceName,
|
||||
loginRedirectUrl,
|
||||
appTokenUrl,
|
||||
stateToken,
|
||||
} = loadState<{
|
||||
client: string
|
||||
direct?: boolean
|
||||
instanceName: string
|
||||
loginRedirectUrl: string
|
||||
appTokenUrl: string
|
||||
stateToken: string
|
||||
}>('core', 'loginFlowAuth')
|
||||
|
||||
const useAppTokenLogin = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoginFlowContainer :heading="t('core', 'Connect to your account')">
|
||||
<NcNoteCard type="info">
|
||||
{{ t('core', 'Please log in before granting "{client}" access to your {instanceName} account.', { client, instanceName }) }}
|
||||
</NcNoteCard>
|
||||
|
||||
<NcNoteCard type="warning" :heading="t('core', 'Security warning')">
|
||||
{{ t('core', '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.') }}
|
||||
</NcNoteCard>
|
||||
|
||||
<NcButton
|
||||
v-if="!useAppTokenLogin"
|
||||
:class="$style.loginFlowAuth__button"
|
||||
:href="loginRedirectUrl"
|
||||
variant="primary">
|
||||
{{ t('core', 'Log in') }}
|
||||
</NcButton>
|
||||
|
||||
<LoginFlowAuthAppToken
|
||||
v-else
|
||||
:app-token-url="appTokenUrl"
|
||||
:direct="direct ?? false"
|
||||
:state-token="stateToken" />
|
||||
|
||||
<NcButton
|
||||
:class="$style.loginFlowAuth__button"
|
||||
variant="tertiary"
|
||||
@click="useAppTokenLogin = !useAppTokenLogin">
|
||||
{{ useAppTokenLogin ? t('core', 'Log in using password') : t('core', 'Alternative log in using app password') }}
|
||||
</NcButton>
|
||||
</LoginFlowContainer>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.loginFlowAuth__button {
|
||||
margin-top: 0.5rem;
|
||||
margin-inline: auto;
|
||||
min-width: 50% !important;
|
||||
}
|
||||
</style>
|
||||
20
core/src/views/LoginFlowDone.vue
Normal file
20
core/src/views/LoginFlowDone.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import LoginFlowContainer from '../components/LoginFlow/LoginFlowContainer.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoginFlowContainer :heading="t('core', 'Account connected')">
|
||||
<NcNoteCard type="info">
|
||||
{{ t('core', 'Your client should now be connected!') }}
|
||||
<br>
|
||||
{{ t('core', 'You can close this window.') }}
|
||||
</NcNoteCard>
|
||||
</LoginFlowContainer>
|
||||
</template>
|
||||
106
core/src/views/LoginFlowGrant.vue
Normal file
106
core/src/views/LoginFlowGrant.vue
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { confirmPassword, isPasswordConfirmationRequired, PwdConfirmationMode } from '@nextcloud/password-confirmation'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import LoginFlowContainer from '../components/LoginFlow/LoginFlowContainer.vue'
|
||||
|
||||
const {
|
||||
clientIdentifier,
|
||||
oauthState,
|
||||
providedRedirectUri,
|
||||
|
||||
actionUrl,
|
||||
client,
|
||||
direct,
|
||||
instanceName,
|
||||
stateToken,
|
||||
userDisplayName,
|
||||
userId,
|
||||
} = loadState<{
|
||||
clientIdentifier?: string
|
||||
oauthState?: string
|
||||
providedRedirectUri?: string
|
||||
|
||||
actionUrl: string
|
||||
client: string
|
||||
direct: boolean
|
||||
instanceName: string
|
||||
stateToken: string
|
||||
userId: string
|
||||
userDisplayName: string
|
||||
}>('core', 'loginFlowGrant')
|
||||
|
||||
const requestToken = getRequestToken()
|
||||
|
||||
/**
|
||||
* Handle submit event to confirm password if required
|
||||
*
|
||||
* @param event - The submit event
|
||||
*/
|
||||
async function onSubmit(event: SubmitEvent) {
|
||||
if (isPasswordConfirmationRequired(PwdConfirmationMode.Lax)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
await confirmPassword()
|
||||
;(event.target as HTMLFormElement).submit()
|
||||
return false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoginFlowContainer :heading="t('core', 'Account access')">
|
||||
<NcNoteCard type="info">
|
||||
{{ t('core', 'Currently logged in as {userDisplayName} ({userId}).', { userDisplayName, userId }) }}
|
||||
<br>
|
||||
{{ t('core', 'You are about to grant "{client}" access to your {instanceName} account.', { client, instanceName }) }}
|
||||
</NcNoteCard>
|
||||
|
||||
<form method="POST" :action="actionUrl" @submit="onSubmit">
|
||||
<input type="hidden" name="requesttoken" :value="requestToken">
|
||||
<input type="hidden" name="stateToken" :value="stateToken">
|
||||
|
||||
<input
|
||||
v-if="direct"
|
||||
type="hidden"
|
||||
name="direct"
|
||||
value="1">
|
||||
<input
|
||||
v-if="clientIdentifier !== undefined"
|
||||
type="hidden"
|
||||
name="clientIdentifier"
|
||||
:value="clientIdentifier">
|
||||
<input
|
||||
v-if="oauthState !== undefined"
|
||||
type="hidden"
|
||||
name="oauthState"
|
||||
:value="oauthState">
|
||||
<input
|
||||
v-if="providedRedirectUri !== undefined"
|
||||
type="hidden"
|
||||
name="providedRedirectUri"
|
||||
:value="providedRedirectUri">
|
||||
|
||||
<NcButton :class="$style.loginFlowGrant__button" type="submit" variant="primary">
|
||||
{{ t('core', 'Grant access') }}
|
||||
</NcButton>
|
||||
</form>
|
||||
</LoginFlowContainer>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.loginFlowGrant__button {
|
||||
margin-top: 0.5rem;
|
||||
margin-inline: auto;
|
||||
min-width: 50% !important;
|
||||
}
|
||||
</style>
|
||||
8
core/templates/loginflow.php
Normal file
8
core/templates/loginflow.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
?>
|
||||
|
||||
<div id="core-loginflow"></div>
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
\OCP\Util::addScript('core', 'login/authpicker', 'core');
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Connect to your account')) ?></h2>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('Please log in before granting %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<div class="notecard warning">
|
||||
<h3><?php p($l->t('Security warning')) ?></h3>
|
||||
<p>
|
||||
<?php p($l->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.')) ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form id="login-form" action="<?php p($urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', ['stateToken' => $_['stateToken'], 'clientIdentifier' => $_['clientIdentifier'], 'oauthState' => $_['oauthState'], 'user' => $_['user'], 'direct' => $_['direct'], 'providedRedirectUri' => $_['providedRedirectUri']])) ?>" method="get">
|
||||
<input type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Log in')) ?>" disabled>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<form action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.apptokenRedirect')); ?>" method="post" id="app-token-login-field" class="hidden">
|
||||
<p class="grouptop">
|
||||
<input type="text" name="user" id="user" placeholder="<?php p($l->t('Login')) ?>">
|
||||
<label for="user" class="infield"><?php p($l->t('Login')) ?></label>
|
||||
</p>
|
||||
<p class="groupbottom">
|
||||
<input type="password" name="password" id="password" placeholder="<?php p($l->t('App password')) ?>">
|
||||
<label for="password" class="infield"><?php p($l->t('Password')) ?></label>
|
||||
</p>
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>">
|
||||
<?php if ($_['direct'] !== 0) { ?>
|
||||
<input type="hidden" name="direct" value="<?php p($_['direct']) ?>">
|
||||
<?php } ?>
|
||||
<input id="submit-app-token-login" type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Grant access')) ?>">
|
||||
</form>
|
||||
|
||||
<?php if (empty($_['oauthState'])): ?>
|
||||
<a id="app-token-login" class="apptoken-link" href="#"><?php p($l->t('Alternative log in using app password')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
\OCP\Util::addScript('core', 'login/grant', 'core');
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window small">
|
||||
<h2><?php p($l->t('Account access')) ?></h2>
|
||||
<p class="info">
|
||||
<?php p($l->t('Currently logged in as %1$s (%2$s).', [
|
||||
$_['userDisplayName'],
|
||||
$_['userId'],
|
||||
])) ?>
|
||||
</p>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('You are about to grant %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form method="POST" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.generateAppPassword')) ?>">
|
||||
<input type="hidden" name="clientIdentifier" value="<?php p($_['clientIdentifier']) ?>" />
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<input type="hidden" name="oauthState" value="<?php p($_['oauthState']) ?>" />
|
||||
<input type="hidden" name="providedRedirectUri" value="<?php p($_['providedRedirectUri']) ?>">
|
||||
<?php if ($_['direct']) { ?>
|
||||
<input type="hidden" name="direct" value="1" />
|
||||
<?php } ?>
|
||||
<div id="submit-wrapper">
|
||||
<input type="submit" class="login primary icon-confirm-white" title="" value="<?php p($l->t('Grant access')); ?>" />
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
style('core', 'login/authpicker');
|
||||
\OCP\Util::addScript('core', 'login/authpicker', 'core');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Connect to your account')) ?></h2>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('Please log in before granting %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<div class="notecard warning">
|
||||
<h3><?php p($l->t('Security warning')) ?></h3>
|
||||
<p>
|
||||
<?php p($l->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.')) ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form id="login-form" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.grantPage', ['stateToken' => $_['stateToken'], 'user' => $_['user'], 'direct' => $_['direct'] ?? 0])) ?>" method="get">
|
||||
<input type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Log in')) ?>" disabled>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<form action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.apptokenRedirect')); ?>" method="post" id="app-token-login-field" class="hidden">
|
||||
<p class="grouptop">
|
||||
<input type="text" name="user" id="user" placeholder="<?php p($l->t('Login')) ?>">
|
||||
<label for="user" class="infield"><?php p($l->t('Login')) ?></label>
|
||||
</p>
|
||||
<p class="groupbottom">
|
||||
<input type="password" name="password" id="password" placeholder="<?php p($l->t('App password')) ?>">
|
||||
<label for="password" class="infield"><?php p($l->t('Password')) ?></label>
|
||||
</p>
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>">
|
||||
<input id="submit-app-token-login" type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Grant access')) ?>">
|
||||
</form>
|
||||
|
||||
<?php if (empty($_['oauthState'])): ?>
|
||||
<a id="app-token-login" class="apptoken-link" href="#"><?php p($l->t('Alternative log in using app password')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Account connected')) ?></h2>
|
||||
<p class="info">
|
||||
<?php p($l->t('Your client should now be connected!')) ?><br/>
|
||||
<?php p($l->t('You can close this window.')) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
\OCP\Util::addScript('core', 'login/grant', 'core');
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window small">
|
||||
<h2><?php p($l->t('Account access')) ?></h2>
|
||||
<p class="info">
|
||||
<?php p($l->t('Currently logged in as %1$s (%2$s).', [
|
||||
$_['userDisplayName'],
|
||||
$_['userId'],
|
||||
])) ?>
|
||||
</p>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('You are about to grant %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form method="POST" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.generateAppPassword')) ?>">
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<?php if ($_['direct']) { ?>
|
||||
<input type="hidden" name="direct" value="1" />
|
||||
<?php } ?>
|
||||
<div id="submit-wrapper">
|
||||
<input type="submit" class="login primary icon-confirm-white" title="" value="<?php p($l->t('Grant access')); ?>" />
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue