diff --git a/app/Auth/Access/ExternalAuthService.php b/app/Auth/Access/GroupSyncService.php similarity index 63% rename from app/Auth/Access/ExternalAuthService.php rename to app/Auth/Access/GroupSyncService.php index b2b9302af..ddd539b77 100644 --- a/app/Auth/Access/ExternalAuthService.php +++ b/app/Auth/Access/GroupSyncService.php @@ -4,48 +4,10 @@ namespace BookStack\Auth\Access; 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. @@ -98,17 +60,17 @@ class ExternalAuthService /** * 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 diff --git a/app/Auth/Access/Guards/Saml2SessionGuard.php b/app/Auth/Access/Guards/AsyncExternalBaseSessionGuard.php similarity index 92% rename from app/Auth/Access/Guards/Saml2SessionGuard.php rename to app/Auth/Access/Guards/AsyncExternalBaseSessionGuard.php index eacd5d21e..6677f5b10 100644 --- a/app/Auth/Access/Guards/Saml2SessionGuard.php +++ b/app/Auth/Access/Guards/AsyncExternalBaseSessionGuard.php @@ -10,7 +10,7 @@ namespace BookStack\Auth\Access\Guards; * 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. diff --git a/app/Auth/Access/Guards/OpenIdSessionGuard.php b/app/Auth/Access/Guards/OpenIdSessionGuard.php deleted file mode 100644 index 634464493..000000000 --- a/app/Auth/Access/Guards/OpenIdSessionGuard.php +++ /dev/null @@ -1,79 +0,0 @@ -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; - } -} diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 7bfdb5328..ddd6ada97 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -13,9 +13,10 @@ use Illuminate\Support\Facades\Log; * Class LdapService * Handles any app-specific LDAP tasks. */ -class LdapService extends ExternalAuthService +class LdapService { protected $ldap; + protected $groupSyncService; protected $ldapConnection; protected $userAvatars; protected $config; @@ -24,20 +25,19 @@ class LdapService extends ExternalAuthService /** * 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; } @@ -285,9 +285,7 @@ class LdapService extends ExternalAuthService } $userGroups = $this->groupFilter($user); - $userGroups = $this->getGroupsRecursive($userGroups, []); - - return $userGroups; + return $this->getGroupsRecursive($userGroups, []); } /** @@ -374,7 +372,7 @@ class LdapService extends ExternalAuthService 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']); } /** diff --git a/app/Auth/Access/OpenIdConnectService.php b/app/Auth/Access/OpenIdConnectService.php new file mode 100644 index 000000000..2548aee6e --- /dev/null +++ b/app/Auth/Access/OpenIdConnectService.php @@ -0,0 +1,164 @@ +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; + } +} diff --git a/app/Auth/Access/OpenIdService.php b/app/Auth/Access/OpenIdService.php deleted file mode 100644 index f56ff0b91..000000000 --- a/app/Auth/Access/OpenIdService.php +++ /dev/null @@ -1,257 +0,0 @@ -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; - } -} diff --git a/app/Auth/Access/RegistrationService.php b/app/Auth/Access/RegistrationService.php index 16e3edbb4..48970bd2e 100644 --- a/app/Auth/Access/RegistrationService.php +++ b/app/Auth/Access/RegistrationService.php @@ -11,6 +11,7 @@ use BookStack\Facades\Activity; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use Exception; +use Illuminate\Support\Str; class RegistrationService { @@ -50,6 +51,31 @@ 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. * diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index b1489fbce..8e076f86c 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -17,22 +17,26 @@ use OneLogin\Saml2\ValidationError; * 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; } /** @@ -47,7 +51,7 @@ class Saml2Service extends ExternalAuthService return [ 'url' => $toolKit->login($returnRoute, [], false, false, true), - 'id' => $toolKit->getLastRequestID(), + 'id' => $toolKit->getLastRequestID(), ]; } @@ -196,7 +200,7 @@ class Saml2Service extends ExternalAuthService protected function loadOneloginServiceProviderDetails(): array { $spDetails = [ - 'entityId' => url('/saml2/metadata'), + 'entityId' => url('/saml2/metadata'), 'assertionConsumerService' => [ 'url' => url('/saml2/acs'), ], @@ -207,7 +211,7 @@ class Saml2Service extends ExternalAuthService return [ 'baseurl' => url('/saml2'), - 'sp' => $spDetails, + 'sp' => $spDetails, ]; } @@ -259,6 +263,7 @@ class Saml2Service extends ExternalAuthService /** * 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 { @@ -270,9 +275,9 @@ class Saml2Service extends ExternalAuthService return [ 'external_id' => $externalId, - 'name' => $this->getUserDisplayName($samlAttributes, $externalId), - 'email' => $email, - 'saml_id' => $samlID, + 'name' => $this->getUserDisplayName($samlAttributes, $externalId), + 'email' => $email, + 'saml_id' => $samlID, ]; } @@ -339,8 +344,8 @@ class Saml2Service extends ExternalAuthService 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, ]); } @@ -353,14 +358,17 @@ class Saml2Service extends ExternalAuthService 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'); diff --git a/app/Config/auth.php b/app/Config/auth.php index 5b39bafed..5a5e727e5 100644 --- a/app/Config/auth.php +++ b/app/Config/auth.php @@ -11,7 +11,7 @@ return [ // Method of authentication to use - // Options: standard, ldap, saml2 + // Options: standard, ldap, saml2, oidc 'method' => env('AUTH_METHOD', 'standard'), // Authentication Defaults @@ -26,7 +26,7 @@ return [ // 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', @@ -37,11 +37,11 @@ return [ 'provider' => 'external', ], 'saml2' => [ - 'driver' => 'saml2-session', + 'driver' => 'async-external-session', 'provider' => 'external', ], - 'openid' => [ - 'driver' => 'openid-session', + 'oidc' => [ + 'driver' => 'async-external-session', 'provider' => 'external', ], 'api' => [ diff --git a/app/Exceptions/OpenIdConnectException.php b/app/Exceptions/OpenIdConnectException.php new file mode 100644 index 000000000..d58585732 --- /dev/null +++ b/app/Exceptions/OpenIdConnectException.php @@ -0,0 +1,6 @@ +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(); + } +} diff --git a/app/Http/Controllers/Auth/OpenIdController.php b/app/Http/Controllers/Auth/OpenIdController.php deleted file mode 100644 index 8e475ffdb..000000000 --- a/app/Http/Controllers/Auth/OpenIdController.php +++ /dev/null @@ -1,70 +0,0 @@ -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(); - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index a2e7f1dc1..804a22bc0 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -20,6 +20,5 @@ class VerifyCsrfToken extends Middleware */ protected $except = [ 'saml2/*', - 'openid/*', ]; } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index cd90cc849..bc7caa195 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -5,11 +5,9 @@ namespace BookStack\Providers; 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; @@ -39,27 +37,16 @@ class AuthServiceProvider extends 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] - ); - }); } /** diff --git a/resources/icons/oidc.svg b/resources/icons/oidc.svg new file mode 100644 index 000000000..a9a2994a7 --- /dev/null +++ b/resources/icons/oidc.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index 44f0c25a0..f023b6bdf 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -23,10 +23,10 @@ return [ '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.', diff --git a/resources/views/auth/parts/login-form-oidc.blade.php b/resources/views/auth/parts/login-form-oidc.blade.php new file mode 100644 index 000000000..e5e1b70fc --- /dev/null +++ b/resources/views/auth/parts/login-form-oidc.blade.php @@ -0,0 +1,11 @@ +
+ {!! csrf_field() !!} + +
+ +
+ +
diff --git a/resources/views/auth/parts/login-form-openid.blade.php b/resources/views/auth/parts/login-form-openid.blade.php deleted file mode 100644 index ba975ebf4..000000000 --- a/resources/views/auth/parts/login-form-openid.blade.php +++ /dev/null @@ -1,11 +0,0 @@ -
- {!! csrf_field() !!} - -
- -
- -
diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index cac585a65..2311ce3e0 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -73,8 +73,6 @@
  • @if(config('auth.method') === 'saml2') @icon('logout'){{ trans('auth.logout') }} - @elseif(config('auth.method') === 'openid') - @icon('logout'){{ trans('auth.logout') }} @else @icon('logout'){{ trans('auth.logout') }} @endif diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index 8d63244e1..5fe5f3685 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -221,7 +221,7 @@ 'label' => trans('settings.reg_enable_toggle') ]) - @if(in_array(config('auth.method'), ['ldap', 'saml2', 'openid'])) + @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc']))
    {{ trans('settings.reg_enable_external_warning') }}
    @endif diff --git a/resources/views/settings/roles/parts/form.blade.php b/resources/views/settings/roles/parts/form.blade.php index 3f7f8fd1f..9cea9e1fb 100644 --- a/resources/views/settings/roles/parts/form.blade.php +++ b/resources/views/settings/roles/parts/form.blade.php @@ -22,7 +22,7 @@ @include('form.checkbox', ['name' => 'mfa_enforced', 'label' => trans('settings.role_mfa_enforced') ]) - @if(in_array(config('auth.method'), ['ldap', 'saml2', 'openid'])) + @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc']))
    @include('form.text', ['name' => 'external_auth_id']) diff --git a/resources/views/users/parts/form.blade.php b/resources/views/users/parts/form.blade.php index ef8c611ef..2a5002c3b 100644 --- a/resources/views/users/parts/form.blade.php +++ b/resources/views/users/parts/form.blade.php @@ -25,7 +25,7 @@
    -@if(in_array($authMethod, ['ldap', 'saml2', 'openid']) && userCan('users-manage')) +@if(in_array($authMethod, ['ldap', 'saml2', 'oidc']) && userCan('users-manage'))
    diff --git a/routes/web.php b/routes/web.php index fb4282539..72e0392cc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -267,10 +267,9 @@ Route::get('/saml2/metadata', 'Auth\Saml2Controller@metadata'); 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');