- Removed uneeded custom refresh or logout actions for OIDC.
- Restructured how the services and guards are setup for external auth
systems. SAML2 and OIDC now directly share a lot more logic.
- Renamed any OpenId references to OIDC or OpenIdConnect
- Removed non-required CSRF excemption for OIDC
Not tested, Come to roadblock due to lack of PHP8 support in upstream
dependancies. Certificate was deemed to be non-valid on every test
attempt due to changes in PHP8.
use BookStack\Auth\Role;
use BookStack\Auth\User;
-use BookStack\Exceptions\UserRegistrationException;
use Illuminate\Support\Collection;
-use Illuminate\Support\Str;
-class ExternalAuthService
+class GroupSyncService
{
- protected $registrationService;
- protected $user;
-
- /**
- * ExternalAuthService base constructor.
- */
- public function __construct(RegistrationService $registrationService, User $user)
- {
- $this->registrationService = $registrationService;
- $this->user = $user;
- }
-
- /**
- * Get the user from the database for the specified details.
- * @throws UserRegistrationException
- */
- protected function getOrRegisterUser(array $userDetails): ?User
- {
- $user = User::query()
- ->where('external_auth_id', '=', $userDetails['external_id'])
- ->first();
-
- if (is_null($user)) {
- $userData = [
- 'name' => $userDetails['name'],
- 'email' => $userDetails['email'],
- 'password' => Str::random(32),
- 'external_auth_id' => $userDetails['external_id'],
- ];
-
- $user = $this->registrationService->registerUser($userData, null, false);
- }
-
- return $user;
- }
-
/**
* Check a role against an array of group names to see if it matches.
* Checked against role 'external_auth_id' if set otherwise the name of the role.
/**
* Sync the groups to the user roles for the current user.
*/
- public function syncWithGroups(User $user, array $userGroups): void
+ public function syncUserWithFoundGroups(User $user, array $userGroups, bool $detachExisting): void
{
// Get the ids for the roles from the names
$groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups);
// Sync groups
- if ($this->config['remove_from_groups']) {
+ if ($detachExisting) {
$user->roles()->sync($groupsAsRoles);
$user->attachDefaultRole();
} else {
$user->roles()->syncWithoutDetaching($groupsAsRoles);
}
}
-}
+}
\ No newline at end of file
* via the Saml2 controller & Saml2Service. This class provides a safer, thin
* version of SessionGuard.
*/
-class Saml2SessionGuard extends ExternalBaseSessionGuard
+class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard
{
/**
* Validate a user's credentials.
+++ /dev/null
-<?php
-
-namespace BookStack\Auth\Access\Guards;
-
-use BookStack\Auth\Access\OpenIdService;
-use BookStack\Auth\Access\RegistrationService;
-use Illuminate\Contracts\Auth\UserProvider;
-use Illuminate\Contracts\Session\Session;
-
-/**
- * OpenId Session Guard
- *
- * The OpenId login process is async in nature meaning it does not fit very well
- * into the default laravel 'Guard' auth flow. Instead most of the logic is done
- * via the OpenId controller & OpenIdService. This class provides a safer, thin
- * version of SessionGuard.
- *
- * @package BookStack\Auth\Access\Guards
- */
-class OpenIdSessionGuard extends ExternalBaseSessionGuard
-{
-
- protected $openidService;
-
- /**
- * OpenIdSessionGuard constructor.
- */
- public function __construct(
- $name,
- UserProvider $provider,
- Session $session,
- OpenIdService $openidService,
- RegistrationService $registrationService
- ) {
- $this->openidService = $openidService;
- parent::__construct($name, $provider, $session, $registrationService);
- }
-
- /**
- * Get the currently authenticated user.
- *
- * @return \Illuminate\Contracts\Auth\Authenticatable|null
- */
- public function user()
- {
- // retrieve the current user
- $user = parent::user();
-
- // refresh the current user
- if ($user && !$this->openidService->refresh()) {
- $this->user = null;
- }
-
- return $this->user;
- }
-
- /**
- * Validate a user's credentials.
- *
- * @param array $credentials
- * @return bool
- */
- public function validate(array $credentials = [])
- {
- return false;
- }
-
- /**
- * Attempt to authenticate a user using the given credentials.
- *
- * @param array $credentials
- * @param bool $remember
- * @return bool
- */
- public function attempt(array $credentials = [], $remember = false)
- {
- return false;
- }
-}
* Class LdapService
* Handles any app-specific LDAP tasks.
*/
-class LdapService extends ExternalAuthService
+class LdapService
{
protected $ldap;
+ protected $groupSyncService;
protected $ldapConnection;
protected $userAvatars;
protected $config;
/**
* LdapService constructor.
*/
- public function __construct(Ldap $ldap, UserAvatars $userAvatars)
+ public function __construct(Ldap $ldap, UserAvatars $userAvatars, GroupSyncService $groupSyncService)
{
$this->ldap = $ldap;
$this->userAvatars = $userAvatars;
+ $this->groupSyncService = $groupSyncService;
$this->config = config('services.ldap');
$this->enabled = config('auth.method') === 'ldap';
}
/**
* Check if groups should be synced.
- *
- * @return bool
*/
- public function shouldSyncGroups()
+ public function shouldSyncGroups(): bool
{
return $this->enabled && $this->config['user_to_groups'] !== false;
}
}
$userGroups = $this->groupFilter($user);
- $userGroups = $this->getGroupsRecursive($userGroups, []);
-
- return $userGroups;
+ return $this->getGroupsRecursive($userGroups, []);
}
/**
public function syncGroups(User $user, string $username)
{
$userLdapGroups = $this->getUserGroups($username);
- $this->syncWithGroups($user, $userLdapGroups);
+ $this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config['remove_from_groups']);
}
/**
--- /dev/null
+<?php namespace BookStack\Auth\Access;
+
+use BookStack\Auth\User;
+use BookStack\Exceptions\JsonDebugException;
+use BookStack\Exceptions\OpenIdConnectException;
+use BookStack\Exceptions\StoppedAuthenticationException;
+use BookStack\Exceptions\UserRegistrationException;
+use Exception;
+use Lcobucci\JWT\Signer\Rsa\Sha256;
+use Lcobucci\JWT\Token;
+use OpenIDConnectClient\AccessToken;
+use OpenIDConnectClient\OpenIDConnectProvider;
+
+/**
+ * Class OpenIdConnectService
+ * Handles any app-specific OIDC tasks.
+ */
+class OpenIdConnectService
+{
+ protected $registrationService;
+ protected $loginService;
+ protected $config;
+
+ /**
+ * OpenIdService constructor.
+ */
+ public function __construct(RegistrationService $registrationService, LoginService $loginService)
+ {
+ $this->config = config('oidc');
+ $this->registrationService = $registrationService;
+ $this->loginService = $loginService;
+ }
+
+ /**
+ * Initiate an authorization flow.
+ * @return array{url: string, state: string}
+ */
+ public function login(): array
+ {
+ $provider = $this->getProvider();
+ return [
+ 'url' => $provider->getAuthorizationUrl(),
+ 'state' => $provider->getState(),
+ ];
+ }
+
+ /**
+ * Process the Authorization response from the authorization server and
+ * return the matching, or new if registration active, user matched to
+ * the authorization server.
+ * Returns null if not authenticated.
+ * @throws Exception
+ */
+ public function processAuthorizeResponse(?string $authorizationCode): ?User
+ {
+ $provider = $this->getProvider();
+
+ // Try to exchange authorization code for access token
+ $accessToken = $provider->getAccessToken('authorization_code', [
+ 'code' => $authorizationCode,
+ ]);
+
+ return $this->processAccessTokenCallback($accessToken);
+ }
+
+ /**
+ * Load the underlying OpenID Connect Provider.
+ */
+ protected function getProvider(): OpenIDConnectProvider
+ {
+ // Setup settings
+ $settings = [
+ 'clientId' => $this->config['client_id'],
+ 'clientSecret' => $this->config['client_secret'],
+ 'idTokenIssuer' => $this->config['issuer'],
+ 'redirectUri' => url('/oidc/redirect'),
+ 'urlAuthorize' => $this->config['authorization_endpoint'],
+ 'urlAccessToken' => $this->config['token_endpoint'],
+ 'urlResourceOwnerDetails' => null,
+ 'publicKey' => $this->config['jwt_public_key'],
+ 'scopes' => 'profile email',
+ ];
+
+ // Setup services
+ $services = [
+ 'signer' => new Sha256(),
+ ];
+
+ return new OpenIDConnectProvider($settings, $services);
+ }
+
+ /**
+ * Calculate the display name
+ */
+ protected function getUserDisplayName(Token $token, string $defaultValue): string
+ {
+ $displayNameAttr = $this->config['display_name_claims'];
+
+ $displayName = [];
+ foreach ($displayNameAttr as $dnAttr) {
+ $dnComponent = $token->claims()->get($dnAttr, '');
+ if ($dnComponent !== '') {
+ $displayName[] = $dnComponent;
+ }
+ }
+
+ if (count($displayName) == 0) {
+ $displayName[] = $defaultValue;
+ }
+
+ return implode(' ', $displayName);
+ }
+
+ /**
+ * Extract the details of a user from an ID token.
+ * @return array{name: string, email: string, external_id: string}
+ */
+ protected function getUserDetails(Token $token): array
+ {
+ $id = $token->claims()->get('sub');
+ return [
+ 'external_id' => $id,
+ 'email' => $token->claims()->get('email'),
+ 'name' => $this->getUserDisplayName($token, $id),
+ ];
+ }
+
+ /**
+ * Processes a received access token for a user. Login the user when
+ * they exist, optionally registering them automatically.
+ * @throws OpenIdConnectException
+ * @throws JsonDebugException
+ * @throws UserRegistrationException
+ * @throws StoppedAuthenticationException
+ */
+ protected function processAccessTokenCallback(AccessToken $accessToken): User
+ {
+ $userDetails = $this->getUserDetails($accessToken->getIdToken());
+ $isLoggedIn = auth()->check();
+
+ if ($this->config['dump_user_details']) {
+ throw new JsonDebugException($accessToken->jsonSerialize());
+ }
+
+ if ($userDetails['email'] === null) {
+ throw new OpenIdConnectException(trans('errors.oidc_no_email_address'));
+ }
+
+ if ($isLoggedIn) {
+ throw new OpenIdConnectException(trans('errors.oidc_already_logged_in'), '/login');
+ }
+
+ $user = $this->registrationService->findOrRegister(
+ $userDetails['name'], $userDetails['email'], $userDetails['external_id']
+ );
+
+ if ($user === null) {
+ throw new OpenIdConnectException(trans('errors.oidc_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
+ }
+
+ $this->loginService->login($user, 'oidc');
+ return $user;
+ }
+}
+++ /dev/null
-<?php namespace BookStack\Auth\Access;
-
-use BookStack\Auth\User;
-use BookStack\Exceptions\JsonDebugException;
-use BookStack\Exceptions\OpenIdException;
-use BookStack\Exceptions\UserRegistrationException;
-use Exception;
-use Lcobucci\JWT\Signer\Rsa\Sha256;
-use Lcobucci\JWT\Token;
-use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
-use OpenIDConnectClient\AccessToken;
-use OpenIDConnectClient\Exception\InvalidTokenException;
-use OpenIDConnectClient\OpenIDConnectProvider;
-
-/**
- * Class OpenIdService
- * Handles any app-specific OpenId tasks.
- */
-class OpenIdService extends ExternalAuthService
-{
- protected $config;
-
- /**
- * OpenIdService constructor.
- */
- public function __construct(RegistrationService $registrationService, User $user)
- {
- parent::__construct($registrationService, $user);
-
- $this->config = config('oidc');
- }
-
- /**
- * Initiate an authorization flow.
- * @throws Exception
- */
- public function login(): array
- {
- $provider = $this->getProvider();
- return [
- 'url' => $provider->getAuthorizationUrl(),
- 'state' => $provider->getState(),
- ];
- }
-
- /**
- * Initiate a logout flow.
- */
- public function logout(): array
- {
- $this->actionLogout();
- $url = '/';
- $id = null;
-
- return ['url' => $url, 'id' => $id];
- }
-
- /**
- * Refresh the currently logged in user.
- * @throws Exception
- */
- public function refresh(): bool
- {
- // Retrieve access token for current session
- $json = session()->get('openid_token');
-
- // If no access token was found, reject the refresh
- if (!$json) {
- $this->actionLogout();
- return false;
- }
-
- $accessToken = new AccessToken(json_decode($json, true) ?? []);
-
- // If the token is not expired, refreshing isn't necessary
- if ($this->isUnexpired($accessToken)) {
- return true;
- }
-
- // Try to obtain refreshed access token
- try {
- $newAccessToken = $this->refreshAccessToken($accessToken);
- } catch (Exception $e) {
- // Log out if an unknown problem arises
- $this->actionLogout();
- throw $e;
- }
-
- // If a token was obtained, update the access token, otherwise log out
- if ($newAccessToken !== null) {
- session()->put('openid_token', json_encode($newAccessToken));
- return true;
- } else {
- $this->actionLogout();
- return false;
- }
- }
-
- /**
- * Check whether an access token or OpenID token isn't expired.
- */
- protected function isUnexpired(AccessToken $accessToken): bool
- {
- $idToken = $accessToken->getIdToken();
-
- $accessTokenUnexpired = $accessToken->getExpires() && !$accessToken->hasExpired();
- $idTokenUnexpired = !$idToken || !$idToken->isExpired();
-
- return $accessTokenUnexpired && $idTokenUnexpired;
- }
-
- /**
- * Generate an updated access token, through the associated refresh token.
- * @throws Exception
- */
- protected function refreshAccessToken(AccessToken $accessToken): ?AccessToken
- {
- // If no refresh token available, abort
- if ($accessToken->getRefreshToken() === null) {
- return null;
- }
-
- // ID token or access token is expired, we refresh it using the refresh token
- try {
- return $this->getProvider()->getAccessToken('refresh_token', [
- 'refresh_token' => $accessToken->getRefreshToken(),
- ]);
- } catch (IdentityProviderException $e) {
- // Refreshing failed
- return null;
- }
- }
-
- /**
- * Process the Authorization response from the authorization server and
- * return the matching, or new if registration active, user matched to
- * the authorization server.
- * Returns null if not authenticated.
- * @throws Exception
- * @throws InvalidTokenException
- */
- public function processAuthorizeResponse(?string $authorizationCode): ?User
- {
- $provider = $this->getProvider();
-
- // Try to exchange authorization code for access token
- $accessToken = $provider->getAccessToken('authorization_code', [
- 'code' => $authorizationCode,
- ]);
-
- return $this->processAccessTokenCallback($accessToken);
- }
-
- /**
- * Do the required actions to log a user out.
- */
- protected function actionLogout()
- {
- auth()->logout();
- session()->invalidate();
- }
-
- /**
- * Load the underlying OpenID Connect Provider.
- */
- protected function getProvider(): OpenIDConnectProvider
- {
- // Setup settings
- $settings = [
- 'clientId' => $this->config['client_id'],
- 'clientSecret' => $this->config['client_secret'],
- 'idTokenIssuer' => $this->config['issuer'],
- 'redirectUri' => url('/openid/redirect'),
- 'urlAuthorize' => $this->config['authorization_endpoint'],
- 'urlAccessToken' => $this->config['token_endpoint'],
- 'urlResourceOwnerDetails' => null,
- 'publicKey' => $this->config['jwt_public_key'],
- 'scopes' => 'profile email',
- ];
-
- // Setup services
- $services = [
- 'signer' => new Sha256(),
- ];
-
- return new OpenIDConnectProvider($settings, $services);
- }
-
- /**
- * Calculate the display name
- */
- protected function getUserDisplayName(Token $token, string $defaultValue): string
- {
- $displayNameAttr = $this->config['display_name_claims'];
-
- $displayName = [];
- foreach ($displayNameAttr as $dnAttr) {
- $dnComponent = $token->claims()->get($dnAttr, '');
- if ($dnComponent !== '') {
- $displayName[] = $dnComponent;
- }
- }
-
- if (count($displayName) == 0) {
- $displayName[] = $defaultValue;
- }
-
- return implode(' ', $displayName);;
- }
-
- /**
- * Extract the details of a user from an ID token.
- */
- protected function getUserDetails(Token $token): array
- {
- $id = $token->claims()->get('sub');
- return [
- 'external_id' => $id,
- 'email' => $token->claims()->get('email'),
- 'name' => $this->getUserDisplayName($token, $id),
- ];
- }
-
- /**
- * Processes a received access token for a user. Login the user when
- * they exist, optionally registering them automatically.
- * @throws OpenIdException
- * @throws JsonDebugException
- * @throws UserRegistrationException
- */
- public function processAccessTokenCallback(AccessToken $accessToken): User
- {
- $userDetails = $this->getUserDetails($accessToken->getIdToken());
- $isLoggedIn = auth()->check();
-
- if ($this->config['dump_user_details']) {
- throw new JsonDebugException($accessToken->jsonSerialize());
- }
-
- if ($userDetails['email'] === null) {
- throw new OpenIdException(trans('errors.openid_no_email_address'));
- }
-
- if ($isLoggedIn) {
- throw new OpenIdException(trans('errors.openid_already_logged_in'), '/login');
- }
-
- $user = $this->getOrRegisterUser($userDetails);
- if ($user === null) {
- throw new OpenIdException(trans('errors.openid_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
- }
-
- auth()->login($user);
- session()->put('openid_token', json_encode($accessToken));
- return $user;
- }
-}
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use Exception;
+use Illuminate\Support\Str;
class RegistrationService
{
return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
}
+ /**
+ * Attempt to find a user in the system otherwise register them as a new
+ * user. For use with external auth systems since password is auto-generated.
+ * @throws UserRegistrationException
+ */
+ public function findOrRegister(string $name, string $email, string $externalId): User
+ {
+ $user = User::query()
+ ->where('external_auth_id', '=', $externalId)
+ ->first();
+
+ if (is_null($user)) {
+ $userData = [
+ 'name' => $name,
+ 'email' => $email,
+ 'password' => Str::random(32),
+ 'external_auth_id' => $externalId,
+ ];
+
+ $user = $this->registerUser($userData, null, false);
+ }
+
+ return $user;
+ }
+
/**
* The registrations flow for all users.
*
* Class Saml2Service
* Handles any app-specific SAML tasks.
*/
-class Saml2Service extends ExternalAuthService
+class Saml2Service
{
protected $config;
protected $registrationService;
protected $loginService;
+ protected $groupSyncService;
/**
* Saml2Service constructor.
*/
- public function __construct(RegistrationService $registrationService, LoginService $loginService, User $user)
+ public function __construct(
+ RegistrationService $registrationService,
+ LoginService $loginService,
+ GroupSyncService $groupSyncService
+ )
{
- parent::__construct($registrationService, $user);
-
$this->config = config('saml2');
$this->registrationService = $registrationService;
$this->loginService = $loginService;
+ $this->groupSyncService = $groupSyncService;
}
/**
return [
'url' => $toolKit->login($returnRoute, [], false, false, true),
- 'id' => $toolKit->getLastRequestID(),
+ 'id' => $toolKit->getLastRequestID(),
];
}
protected function loadOneloginServiceProviderDetails(): array
{
$spDetails = [
- 'entityId' => url('/saml2/metadata'),
+ 'entityId' => url('/saml2/metadata'),
'assertionConsumerService' => [
'url' => url('/saml2/acs'),
],
return [
'baseurl' => url('/saml2'),
- 'sp' => $spDetails,
+ 'sp' => $spDetails,
];
}
/**
* Extract the details of a user from a SAML response.
+ * @return array{external_id: string, name: string, email: string, saml_id: string}
*/
protected function getUserDetails(string $samlID, $samlAttributes): array
{
return [
'external_id' => $externalId,
- 'name' => $this->getUserDisplayName($samlAttributes, $externalId),
- 'email' => $email,
- 'saml_id' => $samlID,
+ 'name' => $this->getUserDisplayName($samlAttributes, $externalId),
+ 'email' => $email,
+ 'saml_id' => $samlID,
];
}
if ($this->config['dump_user_details']) {
throw new JsonDebugException([
- 'id_from_idp' => $samlID,
- 'attrs_from_idp' => $samlAttributes,
+ 'id_from_idp' => $samlID,
+ 'attrs_from_idp' => $samlAttributes,
'attrs_after_parsing' => $userDetails,
]);
}
throw new SamlException(trans('errors.saml_already_logged_in'), '/login');
}
- $user = $this->getOrRegisterUser($userDetails);
+ $user = $this->registrationService->findOrRegister(
+ $userDetails['name'], $userDetails['email'], $userDetails['external_id']
+ );
+
if ($user === null) {
throw new SamlException(trans('errors.saml_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
}
if ($this->shouldSyncGroups()) {
$groups = $this->getUserGroups($samlAttributes);
- $this->syncWithGroups($user, $groups);
+ $this->groupSyncService->syncUserWithFoundGroups($user, $groups, $this->config['remove_from_groups']);
}
$this->loginService->login($user, 'saml2');
return [
// Method of authentication to use
- // Options: standard, ldap, saml2
+ // Options: standard, ldap, saml2, oidc
'method' => env('AUTH_METHOD', 'standard'),
// Authentication Defaults
// All authentication drivers have a user provider. This defines how the
// users are actually retrieved out of your database or other storage
// mechanisms used by this application to persist your user's data.
- // Supported drivers: "session", "api-token", "ldap-session"
+ // Supported drivers: "session", "api-token", "ldap-session", "async-external-session"
'guards' => [
'standard' => [
'driver' => 'session',
'provider' => 'external',
],
'saml2' => [
- 'driver' => 'saml2-session',
+ 'driver' => 'async-external-session',
'provider' => 'external',
],
- 'openid' => [
- 'driver' => 'openid-session',
+ 'oidc' => [
+ 'driver' => 'async-external-session',
'provider' => 'external',
],
'api' => [
--- /dev/null
+<?php namespace BookStack\Exceptions;
+
+class OpenIdConnectException extends NotifyException
+{
+
+}
+++ /dev/null
-<?php namespace BookStack\Exceptions;
-
-class OpenIdException extends NotifyException
-{
-
-}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Auth\Access\OpenIdConnectService;
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+
+class OpenIdConnectController extends Controller
+{
+
+ protected $oidcService;
+
+ /**
+ * OpenIdController constructor.
+ */
+ public function __construct(OpenIdConnectService $oidcService)
+ {
+ $this->oidcService = $oidcService;
+ $this->middleware('guard:oidc');
+ }
+
+ /**
+ * Start the authorization login flow via OIDC.
+ */
+ public function login()
+ {
+ $loginDetails = $this->oidcService->login();
+ session()->flash('oidc_state', $loginDetails['state']);
+
+ return redirect($loginDetails['url']);
+ }
+
+ /**
+ * Authorization flow redirect.
+ * Processes authorization response from the OIDC Authorization Server.
+ */
+ public function redirect(Request $request)
+ {
+ $storedState = session()->pull('oidc_state');
+ $responseState = $request->query('state');
+
+ if ($storedState !== $responseState) {
+ $this->showErrorNotification(trans('errors.oidc_fail_authed', ['system' => config('oidc.name')]));
+ return redirect('/login');
+ }
+
+ $user = $this->oidcService->processAuthorizeResponse($request->query('code'));
+ if ($user === null) {
+ $this->showErrorNotification(trans('errors.oidc_fail_authed', ['system' => config('oidc.name')]));
+ return redirect('/login');
+ }
+
+ return redirect()->intended();
+ }
+}
+++ /dev/null
-<?php
-
-namespace BookStack\Http\Controllers\Auth;
-
-use BookStack\Auth\Access\OpenIdService;
-use BookStack\Http\Controllers\Controller;
-
-class OpenIdController extends Controller
-{
-
- protected $openidService;
-
- /**
- * OpenIdController constructor.
- */
- public function __construct(OpenIdService $openidService)
- {
- parent::__construct();
- $this->openidService = $openidService;
- $this->middleware('guard:openid');
- }
-
- /**
- * Start the authorization login flow via OpenId Connect.
- */
- public function login()
- {
- $loginDetails = $this->openidService->login();
- session()->flash('openid_state', $loginDetails['state']);
-
- return redirect($loginDetails['url']);
- }
-
- /**
- * Start the logout flow via OpenId Connect.
- */
- public function logout()
- {
- $logoutDetails = $this->openidService->logout();
-
- if ($logoutDetails['id']) {
- session()->flash('saml2_logout_request_id', $logoutDetails['id']);
- }
-
- return redirect($logoutDetails['url']);
- }
-
- /**
- * Authorization flow Redirect.
- * Processes authorization response from the OpenId Connect Authorization Server.
- */
- public function redirect()
- {
- $storedState = session()->pull('openid_state');
- $responseState = request()->query('state');
-
- if ($storedState !== $responseState) {
- $this->showErrorNotification(trans('errors.openid_fail_authed', ['system' => config('saml2.name')]));
- return redirect('/login');
- }
-
- $user = $this->openidService->processAuthorizeResponse(request()->query('code'));
- if ($user === null) {
- $this->showErrorNotification(trans('errors.openid_fail_authed', ['system' => config('saml2.name')]));
- return redirect('/login');
- }
-
- return redirect()->intended();
- }
-}
*/
protected $except = [
'saml2/*',
- 'openid/*',
];
}
use BookStack\Api\ApiTokenGuard;
use BookStack\Auth\Access\ExternalBaseUserProvider;
use BookStack\Auth\Access\Guards\LdapSessionGuard;
-use BookStack\Auth\Access\Guards\Saml2SessionGuard;
-use BookStack\Auth\Access\Guards\OpenIdSessionGuard;
+use BookStack\Auth\Access\Guards\AsyncExternalBaseSessionGuard;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\LoginService;
-use BookStack\Auth\Access\OpenIdService;
use BookStack\Auth\Access\RegistrationService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
);
});
- Auth::extend('saml2-session', function ($app, $name, array $config) {
+ Auth::extend('async-external-session', function ($app, $name, array $config) {
$provider = Auth::createUserProvider($config['provider']);
- return new Saml2SessionGuard(
+ return new AsyncExternalBaseSessionGuard(
$name,
$provider,
$app['session.store'],
$app[RegistrationService::class]
);
});
-
- Auth::extend('openid-session', function ($app, $name, array $config) {
- $provider = Auth::createUserProvider($config['provider']);
- return new OpenIdSessionGuard(
- $name,
- $provider,
- $this->app['session.store'],
- $app[OpenIdService::class],
- $app[RegistrationService::class]
- );
- });
}
/**
--- /dev/null
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M0 0h24v24H0z" fill="none"/>
+ <path d="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
+</svg>
\ No newline at end of file
'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.',
'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
- 'openid_already_logged_in' => 'Already logged in',
- 'openid_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
- 'openid_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
- 'openid_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
+ 'oidc_already_logged_in' => 'Already logged in',
+ 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
+ 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
+ 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
'social_no_action_defined' => 'No action defined',
'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
--- /dev/null
+<form action="{{ url('/oidc/login') }}" method="POST" id="login-form" class="mt-l">
+ {!! csrf_field() !!}
+
+ <div>
+ <button id="oidc-login" class="button outline svg">
+ @icon('oidc')
+ <span>{{ trans('auth.log_in_with', ['socialDriver' => config('oidc.name')]) }}</span>
+ </button>
+ </div>
+
+</form>
+++ /dev/null
-<form action="{{ url('/openid/login') }}" method="POST" id="login-form" class="mt-l">
- {!! csrf_field() !!}
-
- <div>
- <button id="saml-login" class="button outline block svg">
- @icon('saml2')
- <span>{{ trans('auth.log_in_with', ['socialDriver' => config('openid.name')]) }}</span>
- </button>
- </div>
-
-</form>
<li>
@if(config('auth.method') === 'saml2')
<a href="{{ url('/saml2/logout') }}">@icon('logout'){{ trans('auth.logout') }}</a>
- @elseif(config('auth.method') === 'openid')
- <a href="{{ url('/openid/logout') }}">@icon('logout'){{ trans('auth.logout') }}</a>
@else
<a href="{{ url('/logout') }}">@icon('logout'){{ trans('auth.logout') }}</a>
@endif
'label' => trans('settings.reg_enable_toggle')
])
- @if(in_array(config('auth.method'), ['ldap', 'saml2', 'openid']))
+ @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc']))
<div class="text-warn text-small mb-l">{{ trans('settings.reg_enable_external_warning') }}</div>
@endif
@include('form.checkbox', ['name' => 'mfa_enforced', 'label' => trans('settings.role_mfa_enforced') ])
</div>
- @if(in_array(config('auth.method'), ['ldap', 'saml2', 'openid']))
+ @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc']))
<div class="form-group">
<label for="name">{{ trans('settings.role_external_auth_id') }}</label>
@include('form.text', ['name' => 'external_auth_id'])
</div>
</div>
-@if(in_array($authMethod, ['ldap', 'saml2', 'openid']) && userCan('users-manage'))
+@if(in_array($authMethod, ['ldap', 'saml2', 'oidc']) && userCan('users-manage'))
<div class="grid half gap-xl v-center">
<div>
<label class="setting-list-label">{{ trans('settings.users_external_auth_id') }}</label>
Route::get('/saml2/sls', 'Auth\Saml2Controller@sls');
Route::post('/saml2/acs', 'Auth\Saml2Controller@acs');
-// OpenId routes
-Route::post('/openid/login', 'Auth\OpenIdController@login');
-Route::get('/openid/logout', 'Auth\OpenIdController@logout');
-Route::get('/openid/redirect', 'Auth\OpenIdController@redirect');
+// OIDC routes
+Route::post('/oidc/login', 'Auth\OpenIdConnectController@login');
+Route::get('/oidc/redirect', 'Auth\OpenIdConnectController@redirect');
// User invitation routes
Route::get('/register/invite/{token}', 'Auth\UserInviteController@showSetPassword');