From 12bbe991e69676a8390e6c7584be2a021c15273d Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Thu, 13 Apr 2023 17:35:28 +0200 Subject: [PATCH] Implement OIDC --- app/Auth/OIDC/InvalidConfiguration.php | 11 + app/Auth/OIDC/NetworkIssue.php | 13 ++ app/Auth/OIDC/OIDCController.php | 107 +++++++++ app/Auth/OIDC/OIDCExtendSocialite.php | 13 ++ app/Auth/OIDC/OIDCProvider.php | 216 ++++++++++++++++++ app/Auth/OIDC/OIDCUser.php | 36 +++ .../api/v1/auth/LoginController.php | 16 +- app/Http/Middleware/VerifyCsrfToken.php | 1 + app/Http/Resources/Config.php | 1 + app/Providers/EventServiceProvider.php | 5 + composer.json | 2 + config/services.php | 12 + .../07-advanced/01-external-authentication.md | 76 +++++- lang/de/admin.php | 1 + lang/de/auth.php | 9 + lang/en/admin.php | 1 + lang/en/auth.php | 9 + lang/fr/admin.php | 1 + lang/fr/auth.php | 9 + resources/js/router.js | 3 +- resources/js/views/ExternalLogin.vue | 3 + resources/js/views/Login.vue | 17 +- resources/js/views/Logout.vue | 5 + routes/web.php | 7 + 24 files changed, 562 insertions(+), 12 deletions(-) create mode 100644 app/Auth/OIDC/InvalidConfiguration.php create mode 100644 app/Auth/OIDC/NetworkIssue.php create mode 100644 app/Auth/OIDC/OIDCController.php create mode 100644 app/Auth/OIDC/OIDCExtendSocialite.php create mode 100644 app/Auth/OIDC/OIDCProvider.php create mode 100644 app/Auth/OIDC/OIDCUser.php diff --git a/app/Auth/OIDC/InvalidConfiguration.php b/app/Auth/OIDC/InvalidConfiguration.php new file mode 100644 index 000000000..16fe45086 --- /dev/null +++ b/app/Auth/OIDC/InvalidConfiguration.php @@ -0,0 +1,11 @@ +getMessage()); + } +} diff --git a/app/Auth/OIDC/OIDCController.php b/app/Auth/OIDC/OIDCController.php new file mode 100644 index 000000000..cbd025844 --- /dev/null +++ b/app/Auth/OIDC/OIDCController.php @@ -0,0 +1,107 @@ +middleware('guest'); + } + + public function redirect(Request $request) + { + if ($request->get('redirect')) { + $request->session()->put('redirect_url', $request->input('redirect')); + } + + try { + return Socialite::driver('oidc')->redirect(); + } catch (NetworkIssue $e) { + report($e); + + return redirect('/external_login?error=network_issue'); + } catch (InvalidConfiguration $e) { + report($e); + + return redirect('/external_login?error=invalid_configuration'); + } + } + + public function logout(Request $request) + { + if (isset($_REQUEST['logout_token'])) { + $logout_token = $_REQUEST['logout_token']; + + $claims = Socialite::driver('oidc')->getLogoutTokenClaims($logout_token); + + $lookupSessions = SessionData::where('key', 'oidc_sub')->where('value', $claims->sub)->get(); + foreach ($lookupSessions as $lookupSession) { + $lookupSession->session()->delete(); + } + } + } + + public function callback(Request $request) + { + try { + $oidc_raw_user = Socialite::driver('oidc')->user(); + } catch (NetworkIssue $e) { + report($e); + + return redirect('/external_login?error=network_issue'); + } catch (InvalidConfiguration $e) { + report($e); + + return redirect('/external_login?error=invalid_configuration'); + } catch (ClientException $e) { + report($e); + + return redirect('/external_login?error=invalid_configuration'); + } catch (InvalidStateException $e) { + report($e); + + return redirect('/external_login?error=invalid_state'); + } + + // Create new open-id connect user + try { + $oidc_user = new OIDCUser($oidc_raw_user); + } catch (MissingAttributeException $e) { + return redirect('/external_login?error=missing_attributes'); + } + + // Get eloquent user (existing or new) + $user = $oidc_user->createOrFindEloquentModel('oidc'); + + // Sync attributes + try { + $oidc_user->syncWithEloquentModel($user, config('services.oidc.mapping')->roles); + } catch (MissingAttributeException $e) { + return redirect('/external_login?error=missing_attributes'); + } + + Auth::login($user); + + session(['session_data' => [ + ['key' => 'oidc_sub', 'value' => $oidc_user->getRawAttributes()['sub']], + ]]); + + session()->put('oidc_id_token', $oidc_raw_user->accessTokenResponseBody['id_token']); + + \Log::info('External user {user} has been successfully authenticated.', ['user' => $user->getLogLabel(), 'type' => 'oidc']); + + $url = '/external_login'; + + return redirect($request->session()->has('redirect_url') ? ($url.'?redirect='.urlencode($request->session()->get('redirect_url'))) : $url); + } +} diff --git a/app/Auth/OIDC/OIDCExtendSocialite.php b/app/Auth/OIDC/OIDCExtendSocialite.php new file mode 100644 index 000000000..d5e1dfa20 --- /dev/null +++ b/app/Auth/OIDC/OIDCExtendSocialite.php @@ -0,0 +1,13 @@ +extendSocialite('oidc', OIDCProvider::class); + } +} diff --git a/app/Auth/OIDC/OIDCProvider.php b/app/Auth/OIDC/OIDCProvider.php new file mode 100644 index 000000000..0e55d3bc8 --- /dev/null +++ b/app/Auth/OIDC/OIDCProvider.php @@ -0,0 +1,216 @@ +getConfig('issuer'), '/').'/.well-known/openid-configuration'; + + $cacheKey = 'oidc.config.'.md5($url); + $config = Cache::get($cacheKey); + + if (! $config) { + try { + $response = Http::get($url); + } catch (Exception $e) { + throw new NetworkIssue($e); + } + + if ($response->successful()) { + $config = $response->json(); + Cache::put($cacheKey, $config, $seconds = $this->getConfig('ttl')); + } else { + throw new InvalidConfiguration(); + } + } + + $this->redirectUrl = url($this->getConfig('redirect')); + + return $config[$key] ?? null; + } + + /** + * Get public keys + * + * @return array + */ + private function getJWTKeys() + { + $response = Http::get($this->getOIDCConfig('jwks_uri')); + + return $response->json(); + } + + public function getLogoutTokenClaims($logoutToken) + { + try { + $claims = JWT::decode($logoutToken, JWK::parseKeySet($this->getJWTKeys(), 'RS256')); + + $payload = explode('.', $logoutToken); + $headerRaw = JWT::urlsafeB64Decode($payload[0]); + $header = JWT::jsonDecode($headerRaw); + + // Get the supported algorithms, fallback to RS256 + $supportedAlgs = $this->getOIDCConfig('id_token_signing_alg_values_supported') ?? ['RS256']; + // Get the alg from the header + $alg = $header->alg; + + // Validate the alg (algorithm) Header Parameter + // https://openid.net/specs/openid-connect-backchannel-1_0.html + if (! in_array($alg, $supportedAlgs)) { + throw new Exception('Unsupported alg '.$alg.' header, supported algorithms are '.implode(', ', $supportedAlgs)); + } + if ($alg === 'none') { + throw new Exception('Unsupported alg none'); + } + + if ($this->verifyLogoutTokenClaims($claims)) { + return $claims; + } + } catch (Exception $ex) { + return false; + } + + return false; + } + + private function verifyLogoutTokenClaims($claims) + { + // Verify that the Logout Token doesn't contain a nonce Claim. + if (isset($claims->nonce)) { + return false; + } + + // Verify that the logout token contains the sub + if (! isset($claims->sub)) { + return false; + } + + // Verify that the Logout Token contains an events Claim whose + // value is a JSON object containing the member name + // https://openid.net/specs/openid-connect-backchannel-1_0.html + if (isset($claims->events)) { + $events = (array) $claims->events; + if (! isset($events['http://schemas.openid.net/event/backchannel-logout']) || + ! is_object($events['http://schemas.openid.net/event/backchannel-logout'])) { + return false; + } + } else { + return false; + } + + // Validate the iss + if (strcmp($claims->iss, $this->getOIDCConfig('issuer'))) { + return false; + } + + // Validate the aud + $auds = $claims->aud; + $auds = is_array($auds) ? $auds : [$auds]; + if (! in_array($this->config['client_id'], $auds, true)) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + $this->setScopes(array_merge($this->scopes, $this->getConfig('scopes'))); + + return $this->buildAuthUrlFromBase($this->getOIDCConfig('authorization_endpoint'), $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return $this->getOIDCConfig('token_endpoint'); + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken($token) + { + $response = $this->getHttpClient()->get($this->getOIDCConfig('userinfo_endpoint'), [ + RequestOptions::HEADERS => [ + 'Authorization' => 'Bearer '.$token, + ], + ]); + + return json_decode((string) $response->getBody(), true); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + return (new User())->setRaw($user); + } + + /** + * {@inheritdoc} + */ + protected function getTokenFields($code) + { + return array_merge(parent::getTokenFields($code), [ + 'grant_type' => 'authorization_code', + ]); + } + + public function logout($idToken, $redirect) + { + $signout_endpoint = $this->getOIDCConfig('end_session_endpoint'); + + if (! $signout_endpoint) { + return false; + } + + $signout_params = [ + 'client_id' => $this->config['client_id'], + 'id_token_hint' => $idToken, + 'post_logout_redirect_uri' => $redirect, + ]; + + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&').http_build_query($signout_params, '', '&'); + + return $signout_endpoint; + } +} diff --git a/app/Auth/OIDC/OIDCUser.php b/app/Auth/OIDC/OIDCUser.php new file mode 100644 index 000000000..9b29d0b00 --- /dev/null +++ b/app/Auth/OIDC/OIDCUser.php @@ -0,0 +1,36 @@ +raw_attributes = $oidc_user->user; + + $attributeMap = config('services.oidc.mapping')->attributes; + + foreach ($attributeMap as $attribute => $oidc_attribute) { + foreach ($this->raw_attributes as $attribute_name => $value) { + if (strcasecmp($oidc_attribute, $attribute_name) == 0) { + if (is_array($value)) { + foreach ($value as $sub_value) { + $this->addAttributeValue($attribute, $sub_value); + } + } else { + $this->addAttributeValue($attribute, $value); + } + } + } + } + } + + public function getRawAttributes() + { + return $this->raw_attributes; + } +} diff --git a/app/Http/Controllers/api/v1/auth/LoginController.php b/app/Http/Controllers/api/v1/auth/LoginController.php index 6c87b6cf3..794236f92 100644 --- a/app/Http/Controllers/api/v1/auth/LoginController.php +++ b/app/Http/Controllers/api/v1/auth/LoginController.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; +use Laravel\Socialite\Facades\Socialite; class LoginController extends Controller { @@ -64,12 +65,23 @@ public function logout(Request $request) { // Redirect url after logout $redirect = false; + $externalAuth = false; + $externalSignOut = false; // Logout from external authentication provider switch (\Auth::user()->authenticator) { case 'shibboleth': $redirect = app(ShibbolethProvider::class)->logout(url('/logout')); - + $externalAuth = 'shibboleth'; + $externalSignOut = true; + break; + case 'oidc': + $externalAuth = 'oidc'; + $url = Socialite::driver('oidc')->logout(session()->get('oidc_id_token'), url('/logout')); + if ($url) { + $redirect = $url; + $externalSignOut = true; + } break; } @@ -78,6 +90,8 @@ public function logout(Request $request) return response()->json([ 'redirect' => $redirect, + 'external_auth' => $externalAuth, + 'external_sign_out' => $externalSignOut, ]); } } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 08344dda3..f617a8b0b 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -13,5 +13,6 @@ class VerifyCsrfToken extends Middleware */ protected $except = [ 'auth/shibboleth/logout', + 'auth/oidc/logout', ]; } diff --git a/app/Http/Resources/Config.php b/app/Http/Resources/Config.php index e41619d86..3401d7db1 100644 --- a/app/Http/Resources/Config.php +++ b/app/Http/Resources/Config.php @@ -99,6 +99,7 @@ public function toArray($request) 'local' => config('auth.local.enabled'), 'ldap' => config('ldap.enabled'), 'shibboleth' => config('services.shibboleth.enabled'), + 'oidc' => config('services.oidc.enabled'), ], ]; } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 53d7f46bc..69f53d42d 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,11 +2,13 @@ namespace App\Providers; +use App\Auth\OIDC\OIDCExtendSocialite; use App\Listeners\FailedLoginAttempt; use Illuminate\Auth\Events\Failed; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use SocialiteProviders\Manager\SocialiteWasCalled; class EventServiceProvider extends ServiceProvider { @@ -22,6 +24,9 @@ class EventServiceProvider extends ServiceProvider Failed::class => [ FailedLoginAttempt::class, ], + SocialiteWasCalled::class => [ + OIDCExtendSocialite::class.'@handle', + ], ]; /** diff --git a/composer.json b/composer.json index 26aa1a5ef..8eb16f6a4 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "ext-simplexml": "*", "ext-zip": "*", "directorytree/ldaprecord-laravel": "^3.0", + "firebase/php-jwt": "^6.4", "guzzlehttp/guzzle": "^7.0.1", "laravel/framework": "^11.0", "laravel/horizon": "^5.21", @@ -27,6 +28,7 @@ "spatie/laravel-csp": "^2.9", "spatie/laravel-ignition": "^2.0", "spatie/laravel-settings": "^3.3", + "socialiteproviders/keycloak": "^5.2", "symfony/var-exporter": "^7.0" }, "require-dev": { diff --git a/config/services.php b/config/services.php index 7a0d52935..bc890fb54 100644 --- a/config/services.php +++ b/config/services.php @@ -1,6 +1,7 @@ env('SHIBBOLETH_SESSION_EXPIRES_HEADER', 'shib-session-expires'), 'logout' => env('SHIBBOLETH_LOGOUT_URL', '/Shibboleth.sso/Logout'), ], + + 'oidc' => [ + 'enabled' => $oidcEnabled, + 'issuer' => env('OIDC_ISSUER'), + 'client_id' => env('OIDC_CLIENT_ID'), + 'client_secret' => env('OIDC_CLIENT_SECRET'), + 'redirect' => 'auth/oidc/callback', + 'ttl' => env('OIDC_TTL', 3600), + 'scopes' => explode(',', env('OIDC_SCOPES', 'email,profile')), + 'mapping' => $oidcEnabled ? json_decode(file_get_contents(app_path('Auth/config/oidc_mapping.json'))) : null, + ], ]; diff --git a/docs/docs/administration/07-advanced/01-external-authentication.md b/docs/docs/administration/07-advanced/01-external-authentication.md index e7b5ae5ac..4c2ed3bab 100644 --- a/docs/docs/administration/07-advanced/01-external-authentication.md +++ b/docs/docs/administration/07-advanced/01-external-authentication.md @@ -11,7 +11,8 @@ PILOS has two types of users: Local users can be created by administrators. They can log in to the system with the combination of email address and password. Via PILOS, an email can be sent to the user upon creation, also a password reset function can be activated. **External users** -In large environments it is impractical to manage all users in PILOS. Therefore PILOS can be connected to external authentication systems. LDAP and Shibboleth are available as interfaces. All authentication providers can be operated in parallel, but none of them more than once. +In large environments it is impractical to manage all users in PILOS. Therefore PILOS can be connected to external authentication systems. LDAP, Shibboleth and OpenID-Connect are available as interfaces. All three authentication providers can be operated in parallel, but none of them more than once. + ## Setup of external authenticators @@ -97,6 +98,31 @@ To enable Shibboleth, you need to enable it in the `.env` file. SHIBBOLETH_ENABLED=true ``` +### Open-ID Connect + +To enable Open-ID Connect, you need to add/set the following options in the `.env` file and adjust to your needs. + +The required `openid` scope is always present, even if not explicitly set. If you need more scopes to get all required attributes, add them with a comma. + +The `OIDC_TTL` option defines the time in seconds how long the metadata is cached for, so that the metadata does not have to be reloaded with every request. + +``` +# Open-ID Connect config +OIDC_ENABLED=true +OIDC_CLIENT_ID=my_client_id +OIDC_CLIENT_SECRET=my_client_secret +OIDC_ISSUER=http://idp.university.org +OIDC_SCOPES="profile,email" +OIDC_TTL=3600 +``` + +In your IDP you should configure the following: + +- Redirect URI: https://your-domain.com/auth/oidc/callback +- Post Logout URI: https://your-domain.com/logout +- Backchannel Logout URI: https://your-domain.com/auth/oidc/logout + + ## Configure mapping For each external authenticator the attribute and role mapping needs to be configured. @@ -106,6 +132,7 @@ The mapping is defined in a JSON file, which is stored in the directory `app/Aut |-----------------|-------------------------| | LDAP | ldap_mapping.json | | Shibboleth | shibboleth_mapping.json | +| Open-ID Connect | oidc_mapping.json | ### Attribute mapping @@ -167,15 +194,14 @@ The negation of arrays means: Check that regular expression doesn't match on any If the `all` attribute is also true: Check that regular expression doesn't match matches all entries - ### Examples -### LDAP +#### LDAP -#### Attributes +##### Attributes In this example the LDAP schema uses the common name (CN) as username and has the group memberships in the memberof attribute. -#### Roles +##### Roles - The "superuser" role is assigned to any user whose email ends with @its.university.org and who is in the "cn=admin,ou=Groups,dc=uni,dc=org" group. - The "user" role is given to everyone. @@ -219,14 +245,13 @@ In this example the LDAP schema uses the common name (CN) as username and has th } ``` -### Shibboleth +#### Shibboleth -#### Attributes +##### Attributes The attribute names are the header names in which the attribute values are send by the apache mod_shib to the application. -#### Roles +##### Roles - The "superuser" role is assigned to any user whose email ends with @its.university.org and who has the "staff" affiliation. - - The "user" role is given to everyone. ```json @@ -267,3 +292,36 @@ The attribute names are the header names in which the attribute values are send ] } ``` + +#### Open-ID Connect + +##### Attributes +In this example the Open-ID Connect provider returns the claim `preferred_username` which contains the username and an additional claim `roles` with an array of roles. + +##### Roles +- The "student" role is assigned to any user who has the "student" role. + +```json +{ + "attributes": { + "external_id": "preferred_username", + "first_name": "given_name", + "last_name": "family_name", + "email": "email", + "roles": "roles" + }, + "roles":[ + { + "name":"student", + "disabled":false, + "all":true, + "rules":[ + { + "attribute":"roles", + "regex":"/^(student)$/im" + } + ] + } + ] +} +``` diff --git a/lang/de/admin.php b/lang/de/admin.php index cd0705611..3b32e7b42 100644 --- a/lang/de/admin.php +++ b/lang/de/admin.php @@ -403,6 +403,7 @@ 'authenticator' => [ 'ldap' => 'LDAP', 'local' => 'Lokal', + 'oidc' => 'OIDC', 'shibboleth' => 'Shibboleth', 'title' => 'Anmeldeart', ], diff --git a/lang/de/auth.php b/lang/de/auth.php index e474c5ffe..45fda5e12 100644 --- a/lang/de/auth.php +++ b/lang/de/auth.php @@ -12,7 +12,10 @@ ], 'error' => [ 'login_failed' => 'Anmeldung fehlgeschlagen', + 'invalid_configuration' => 'Die Konfiguration des Anmeldedienstes ist fehlerhaft.', + 'invalid_state' => 'Fehlerhafter Zustand.', 'missing_attributes' => 'Es fehlen Attribute für die Authentifizierung.', + 'network_issue' => 'Es konnte keine Verbindung zum Anmeldedienst aufgebaut werden. Versuchen Sie es später erneut.', 'reason' => 'Fehlergrund', 'shibboleth_session_duplicate_exception' => 'Die Shibboleth-Session ist bereits in Verwendung. Bitte melden Sie sich erneut an.', ], @@ -37,6 +40,12 @@ 'logout_success' => 'Erfolgreich abgemeldet', 'new_password' => 'Neues Passwort', 'new_password_confirmation' => 'Neues Passwort bestätigen', + 'oidc' => [ + 'logout_incomplete' => 'Achtung: Sie sind weiterhin bei OpenID Connect angemeldet.', + 'redirect' => 'Anmelden', + 'tab_title' => 'OpenID Connect', + 'title' => 'Mit OpenID Connect anmelden', + ], 'password' => 'Passwort', 'reset_password' => 'Passwort zurücksetzen', 'send_email_confirm_mail' => 'Es wurde eine Bestätigungsmail an :email gesendet. Bestätigen Sie die Änderung, in dem Sie auf den Link in der E-Mail klicken.', diff --git a/lang/en/admin.php b/lang/en/admin.php index 808579b18..f95a028e2 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -403,6 +403,7 @@ 'authenticator' => [ 'ldap' => 'LDAP', 'local' => 'Local', + 'oidc' => 'OIDC', 'shibboleth' => 'Shibboleth', 'title' => 'Authentication Type', ], diff --git a/lang/en/auth.php b/lang/en/auth.php index ed420cb8f..43b1897b1 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -11,8 +11,11 @@ 'title' => 'Login with Email', ], 'error' => [ + 'invalid_configuration' => 'The configuration of authentication service is incorrect.', + 'invalid_state' => 'Invalid state.', 'login_failed' => 'Login failed', 'missing_attributes' => 'Attributes for authentication are missing.', + 'network_issue' => 'Unable to connect to the authentication service. Try again later.', 'reason' => 'Error reason', 'shibboleth_session_duplicate_exception' => 'The Shibboleth session is already in use. Please log in again.', ], @@ -37,6 +40,12 @@ 'logout_success' => 'Successfully logged out', 'new_password' => 'New password', 'new_password_confirmation' => 'New password confirmation', + 'oidc' => [ + 'logout_incomplete' => 'Attention: You are still logged in to OpenID Connect.', + 'redirect' => 'Log in', + 'tab_title' => 'OpenID Connect', + 'title' => 'Log in with OpenID Connect', + ], 'password' => 'Password', 'reset_password' => 'Reset password', 'send_email_confirm_mail' => 'A verification email has been sent to :email. Please confirm the new email address by clicking on the link in the email.', diff --git a/lang/fr/admin.php b/lang/fr/admin.php index b7c59d612..e91d64a85 100644 --- a/lang/fr/admin.php +++ b/lang/fr/admin.php @@ -313,6 +313,7 @@ 'authenticator' => [ 'ldap' => 'LDAP', 'local' => 'Interne', + 'oidc' => 'OIDC', 'shibboleth' => 'Shibboleth', 'title' => 'Type d\'identification', ], diff --git a/lang/fr/auth.php b/lang/fr/auth.php index e48466b84..109c6f563 100644 --- a/lang/fr/auth.php +++ b/lang/fr/auth.php @@ -11,8 +11,11 @@ 'title' => 'S\'identifier via son e-mail', ], 'error' => [ + 'invalid_configuration' => 'La configuration du service d\'authentification est incorrecte.', + 'invalid_state' => 'État non valide', 'login_failed' => 'Connexion refusée', 'missing_attributes' => 'Informations d\'identification manquantes.', + 'network_issue' => 'Impossible de se connecter au service d\'authentification. Réessayez plus tard.', 'reason' => 'Cause de l\'erreur', 'shibboleth_session_duplicate_exception' => 'Ces données de session sont en cours d\'utilisation. Veuillez vous connecter à nouveau.', ], @@ -37,6 +40,12 @@ 'logout_success' => 'Déconnexion réussie', 'new_password' => 'Nouveau mot de passe', 'new_password_confirmation' => 'Confirmation du nouveau mot de passe', + 'oidc' => [ + 'logout_incomplete' => 'Attention: Vous êtes toujours connecté à OpenID Connect.', + 'redirect' => 'Connexion', + 'tab_title' => 'OpenID Connect', + 'title' => 'Connexion avec OpenID Connect', + ], 'password' => 'Mot de passe', 'reset_password' => 'Réinitialiser le mot de passe', 'send_email_confirm_mail' => 'Un e-mail de vérification a été envoyé à :email. Veuillez confirmer la nouvelle adresse en cliquant sur le lien dans l\'e-mail.', diff --git a/resources/js/router.js b/resources/js/router.js index ad4766230..6ee863085 100644 --- a/resources/js/router.js +++ b/resources/js/router.js @@ -69,7 +69,8 @@ export const routes = [ meta: { guestsOnly: true }, props: route => { return { - message: route.query.message + message: route.query.message, + incompleteWarning: route.params.incompleteWarning }; } }, diff --git a/resources/js/views/ExternalLogin.vue b/resources/js/views/ExternalLogin.vue index 2a9f5ca6c..e6a9bb982 100644 --- a/resources/js/views/ExternalLogin.vue +++ b/resources/js/views/ExternalLogin.vue @@ -5,6 +5,9 @@ diff --git a/resources/js/views/Login.vue b/resources/js/views/Login.vue index d175ced34..bcc4d19d5 100644 --- a/resources/js/views/Login.vue +++ b/resources/js/views/Login.vue @@ -11,6 +11,7 @@ {{ $t('auth.ldap.tab_title') }} {{ $t('auth.shibboleth.tab_title') }} + {{ $t('auth.oidc.tab_title') }} {{ $t('auth.email.tab_title') }} @@ -34,6 +35,14 @@ :redirect-url="shibbolethRedirectUrl" /> + + + - @@ -86,11 +94,18 @@ onMounted(() => { activeTab.value = 'ldap'; } else if (settingsStore.getSetting('auth.shibboleth')) { activeTab.value = 'shibboleth'; + } else if (settingsStore.getSetting('auth.oidc')) { + activeTab.value = 'oidc'; } else { activeTab.value = 'local'; } }); +const oidcRedirectUrl = computed(() => { + const url = '/auth/oidc/redirect'; + return route.query.redirect ? url + '?redirect=' + encodeURIComponent(route.query.redirect) : url; +}); + const shibbolethRedirectUrl = computed(() => { const url = '/auth/shibboleth/redirect'; return route.query.redirect ? url + '?redirect=' + encodeURIComponent(route.query.redirect) : url; diff --git a/resources/js/views/Logout.vue b/resources/js/views/Logout.vue index 737db7bdc..c4d56f51c 100644 --- a/resources/js/views/Logout.vue +++ b/resources/js/views/Logout.vue @@ -7,6 +7,7 @@