Skip to content

Commit

Permalink
Add frontend for single sign on
Browse files Browse the repository at this point in the history
  • Loading branch information
martinlagler committed Mar 6, 2024
1 parent a9c0ebd commit b3c927d
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 31 deletions.
8 changes: 7 additions & 1 deletion src/Sulu/Bundle/AdminBundle/Controller/AdminController.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
Expand Down Expand Up @@ -195,7 +197,8 @@ public function __construct(
string $collaborationInterval,
?bool $collaborationEnabled = null,
?string $passwordPattern = null,
?string $passwordInfoTranslationKey = null
?string $passwordInfoTranslationKey = null,
?bool $hasSingleSignOnProvider = false
) {
$this->urlGenerator = $urlGenerator;
$this->tokenStorage = $tokenStorage;
Expand All @@ -220,6 +223,7 @@ public function __construct(
$this->translations = $translations;
$this->fallbackLocale = $fallbackLocale;
$this->collaborationInterval = $collaborationInterval;
$this->hasSingleSignOnProvider = $hasSingleSignOnProvider;

if (null === $collaborationEnabled) {
@trigger_deprecation('sulu/sulu', '2.3', 'Instantiating the AdminController without the $collaborationEnabled argument is deprecated!');
Expand All @@ -243,6 +247,7 @@ public function indexAction()
'translations' => $this->urlGenerator->generate('sulu_admin.translation'),
'generateUrl' => $this->urlGenerator->generate('sulu_page.post_resourcelocator', ['action' => 'generate']),
'routing' => $this->urlGenerator->generate('fos_js_routing_js'),
'single_sign_on' => $this->hasSingleSignOnProvider,
];

try {
Expand All @@ -261,6 +266,7 @@ public function indexAction()
'password_info_translation_key' => $this->passwordInfoTranslationKey,
'sulu_version' => $this->suluVersion,
'app_version' => $this->appVersion,
'single_sign_on' => $this->hasSingleSignOnProvider,
]
));
}
Expand Down
1 change: 1 addition & 0 deletions src/Sulu/Bundle/AdminBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<argument>%sulu_admin.collaboration_enabled%</argument>
<argument>%sulu_security.password_policy_pattern%</argument>
<argument>%sulu_security.password_policy_info_translation_key%</argument>
<argument>%sulu_security.has_single_sign_on_providers%</argument>

<tag name="sulu.context" context="admin"/>
</service>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class Login extends React.Component<Props> {

handleLoginFormSubmit = (data: LoginFormData) => {
userStore.login(data).then(() => {
if (userStore.hasJsonLogin) {
return;
}

if (userStore.twoFactorMethods && userStore.twoFactorMethods.length > 0) {
action(() => {
this.visibleForm = 'two-factor';
Expand Down Expand Up @@ -135,6 +139,8 @@ class Login extends React.Component<Props> {
<LoginForm
error={userStore.loginError}
loading={userStore.loading}
hasSingleSignOn={userStore.hasSingleSignOn()}
hasOnlyPassword={userStore.hasJsonLogin}
onChangeForm={this.handleChangeToForgotPasswordForm}
onSubmit={this.handleLoginFormSubmit}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type {LoginFormData} from './types';
type Props = {|
error: boolean,
loading: boolean,
hasSingleSignOn: boolean,
hasOnlyPassword: boolean,
onChangeForm: () => void,
onSubmit: (data: LoginFormData) => void,
|};
Expand All @@ -23,6 +25,8 @@ class LoginForm extends React.Component<Props> {
static defaultProps = {
error: false,
loading: false,
hasSingleSignOn: false,
hasOnlyPassword: false,
};

@observable inputRef: ?ElementRef<*>;
Expand All @@ -31,7 +35,7 @@ class LoginForm extends React.Component<Props> {
@observable password: ?string;

@computed get submitButtonDisabled(): boolean {
return !(this.user && this.password);
return !(this.user && this.password) && !((this.user || this.password) && this.props.hasSingleSignOn);
}

@action setInputRef = (ref: ?ElementRef<*>) => {
Expand All @@ -55,6 +59,22 @@ class LoginForm extends React.Component<Props> {
@action handleSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();

if (this.user && this.props.hasSingleSignOn) {
const {onSubmit} = this.props;

onSubmit({
username: this.user,
password: this.password,
});

if (this.user && this.password) {
this.user = undefined;
this.password = undefined;
}

return;
}

if (!this.user || !this.password) {
return;
}
Expand Down Expand Up @@ -84,32 +104,36 @@ class LoginForm extends React.Component<Props> {
</Header>
<form className={formStyles.form} onSubmit={this.handleSubmit}>
<fieldset>
<label className={inputFieldClass}>
<div className={formStyles.labelText}>
{translate('sulu_admin.username_or_email')}
</div>
<Input
autocomplete="username"
icon="su-user"
inputRef={this.setInputRef}
onChange={this.handleUserChange}
valid={!this.props.error}
value={this.user}
/>
</label>
<label className={inputFieldClass}>
<div className={formStyles.labelText}>
{translate('sulu_admin.password')}
</div>
<Input
autocomplete="current-password"
icon="su-lock"
onChange={this.handlePasswordChange}
type="password"
valid={!this.props.error}
value={this.password}
/>
</label>
{(!this.props.hasOnlyPassword) && (
<label className={inputFieldClass}>
<div className={formStyles.labelText}>
{translate('sulu_admin.username_or_email')}
</div>
<Input
autocomplete="username"
icon="su-user"
inputRef={this.setInputRef}
onChange={this.handleUserChange}
valid={!this.props.error}
value={this.user}
/>
</label>
)}
{!this.props.hasSingleSignOn || (this.props.hasSingleSignOn && this.props.hasOnlyPassword) && (
<label className={inputFieldClass}>
<div className={formStyles.labelText}>
{translate('sulu_admin.password')}
</div>
<Input
autocomplete="current-password"
icon="su-lock"
onChange={this.handlePasswordChange}
type="password"
valid={!this.props.error}
value={this.password}
/>
</label>
)}
<div className={formStyles.buttons}>
<Button onClick={this.props.onChangeForm} skin="link">
{translate('sulu_admin.forgot_password')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class UserStore {
@observable loggedIn: boolean = false;
@observable loading: boolean = false;
@observable loginError: boolean = false;
@observable hasJsonLogin: boolean = false;
@observable forgotPasswordSuccess: boolean = false;
@observable twoFactorMethods: Array<string> = [];
@observable twoFactorError: boolean = false;
Expand All @@ -30,6 +31,7 @@ class UserStore {
this.user = undefined;
this.contact = undefined;
this.loginError = false;
this.hasJsonLogin = false;
this.forgotPasswordSuccess = false;
this.twoFactorMethods = [];
this.twoFactorError = false;
Expand All @@ -51,6 +53,10 @@ class UserStore {
this.loginError = loginError;
}

@action setHasJsonLogin(hasJsonLogin: boolean) {
this.hasJsonLogin = hasJsonLogin;
}

@action setForgotPasswordSuccess(forgotPasswordSuccess: boolean) {
this.forgotPasswordSuccess = forgotPasswordSuccess;
}
Expand Down Expand Up @@ -106,6 +112,19 @@ class UserStore {
handleLogin = (data: Object) => {
this.setTwoFactorMethods([]);

if (data.method === 'redirect' && data.url) {
window.location.href = data.url;

return
}

if (data.method === 'json_login') {
this.setHasJsonLogin(true);
this.setLoading(false);

return;
}

if (data.completed === false) {
this.setLoading(false);

Expand All @@ -130,6 +149,10 @@ class UserStore {
this.clear();
}

if (this.hasJsonLogin) {
this.clear();
}

this.setLoading(true);
return initializer.initialize(true).then(() => {
this.setLoading(false);
Expand All @@ -147,6 +170,10 @@ class UserStore {
return Promise.reject(error);
}

if (this.hasJsonLogin) {
this.clear();
}

this.setLoginError(true);
});
};
Expand Down Expand Up @@ -234,6 +261,10 @@ class UserStore {

return new RegExp(pattern).test(password);
}

hasSingleSignOn() {
return Config.endpoints.single_sign_on;
}
}

export default new UserStore();
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
appVersion: app_version,
passwordPattern: password_pattern,
passwordInfoTranslationKey: password_info_translation_key,
SingleSignOn: single_sign_on
} -%}

{% block application -%}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ public function load(array $configs, ContainerBuilder $container)
]
);

foreach ($config['single_sign_on']['providers'] as $domain => $provider) {
dump($provider);
exit;
$container->setParameter('sulu_security.has_single_sign_on_providers', false);

if (array_key_exists('single_sign_on', $config)
&& array_key_exists('providers', $config['single_sign_on'])) {
$container->setParameter(
'sulu_security.has_single_sign_on_providers',
\count($config['single_sign_on']['providers']) > 0
);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Sulu/Bundle/SecurityBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,9 @@

<tag name="sulu_admin.form_metadata_visitor" />
</service>

<service id="sulu_security.open_id_login_subscriber" class="Sulu\Bundle\SecurityBundle\Security\OpenIdLoginSubscriber">
<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>
58 changes: 58 additions & 0 deletions src/Sulu/Bundle/SecurityBundle/Security/OpenIdLoginSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Sulu\Bundle\SecurityBundle\Security;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;

class OpenIdLoginSubscriber implements EventSubscriberInterface
{
final public const OPEN_ID_ATTRIBUTES = '_app_open_id_attributes';

public static function getSubscribedEvents(): array
{
return [
RequestEvent::class => [
['onKernelRequest', 9],
],
];
}

public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}

$request = $event->getRequest();

if (!$request->isMethod('POST')) {
return;
}

$route = $request->attributes->get('_route');
if ('sulu_admin.login_check' !== $route) {
return;
}

$email = $request->request->get('username');
$password = $request->request->get('password');

if (!$email || !\is_string($email)) {
return;
}

// Also return when email and password are set
if ($password) {
return;
}

// Todo: Implement single sign on.
//$event->setResponse(new JsonResponse(['method' => 'redirect', 'url' => 'https://www.google.at'], 200));
$event->setResponse(new JsonResponse(['method' => 'json_login'], 200));
$event->stopPropagation();
}
}

0 comments on commit b3c927d

Please sign in to comment.