X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/41438adbd1dbe8688ff8ff7d3dbb835d1e9650e1..refs/pull/3918/head:/app/Auth/Access/Saml2Service.php diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index 8e076f86c..a95e3b1d2 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -9,6 +9,7 @@ use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Exceptions\UserRegistrationException; use Exception; use OneLogin\Saml2\Auth; +use OneLogin\Saml2\Constants; use OneLogin\Saml2\Error; use OneLogin\Saml2\IdPMetadataParser; use OneLogin\Saml2\ValidationError; @@ -19,20 +20,16 @@ use OneLogin\Saml2\ValidationError; */ class Saml2Service { - protected $config; - protected $registrationService; - protected $loginService; - protected $groupSyncService; + protected array $config; + protected RegistrationService $registrationService; + protected LoginService $loginService; + protected GroupSyncService $groupSyncService; - /** - * Saml2Service constructor. - */ public function __construct( RegistrationService $registrationService, - LoginService $loginService, - GroupSyncService $groupSyncService - ) - { + LoginService $loginService, + GroupSyncService $groupSyncService + ) { $this->config = config('saml2'); $this->registrationService = $registrationService; $this->loginService = $loginService; @@ -51,7 +48,7 @@ class Saml2Service return [ 'url' => $toolKit->login($returnRoute, [], false, false, true), - 'id' => $toolKit->getLastRequestID(), + 'id' => $toolKit->getLastRequestID(), ]; } @@ -60,13 +57,20 @@ class Saml2Service * * @throws Error */ - public function logout(): array + public function logout(User $user): array { $toolKit = $this->getToolkit(); $returnRoute = url('/'); try { - $url = $toolKit->logout($returnRoute, [], null, null, true); + $url = $toolKit->logout( + $returnRoute, + [], + $user->email, + null, + true, + Constants::NAMEID_EMAIL_ADDRESS + ); $id = $toolKit->getLastRequestID(); } catch (Error $error) { if ($error->getCode() !== Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED) { @@ -92,16 +96,20 @@ class Saml2Service * @throws JsonDebugException * @throws UserRegistrationException */ - public function processAcsResponse(?string $requestId): ?User + public function processAcsResponse(?string $requestId, string $samlResponse): ?User { + // The SAML2 toolkit expects the response to be within the $_POST superglobal + // so we need to manually put it back there at this point. + $_POST['SAMLResponse'] = $samlResponse; $toolkit = $this->getToolkit(); $toolkit->processResponse($requestId); $errors = $toolkit->getErrors(); if (!empty($errors)) { - throw new Error( - 'Invalid ACS Response: ' . implode(', ', $errors) - ); + $reason = $toolkit->getLastErrorReason(); + $message = 'Invalid ACS Response; Errors: ' . implode(', ', $errors); + $message .= $reason ? "; Reason: {$reason}" : ''; + throw new Error($message); } if (!$toolkit->isAuthenticated()) { @@ -122,8 +130,13 @@ class Saml2Service public function processSlsResponse(?string $requestId): ?string { $toolkit = $this->getToolkit(); - $redirect = $toolkit->processSLO(true, $requestId, false, null, true); + // The $retrieveParametersFromServer in the call below will mean the library will take the query + // parameters, used for the response signing, from the raw $_SERVER['QUERY_STRING'] + // value so that the exact encoding format is matched when checking the signature. + // This is primarily due to ADFS encoding query params with lowercase percent encoding while + // PHP (And most other sensible providers) standardise on uppercase. + $redirect = $toolkit->processSLO(true, $requestId, true, null, true); $errors = $toolkit->getErrors(); if (!empty($errors)) { @@ -153,7 +166,7 @@ class Saml2Service */ public function metadata(): string { - $toolKit = $this->getToolkit(); + $toolKit = $this->getToolkit(true); $settings = $toolKit->getSettings(); $metadata = $settings->getSPMetadata(); $errors = $settings->validateMetadata($metadata); @@ -174,7 +187,7 @@ class Saml2Service * @throws Error * @throws Exception */ - protected function getToolkit(): Auth + protected function getToolkit(bool $spOnly = false): Auth { $settings = $this->config['onelogin']; $overrides = $this->config['onelogin_overrides'] ?? []; @@ -184,14 +197,14 @@ class Saml2Service } $metaDataSettings = []; - if ($this->config['autoload_from_metadata']) { + if (!$spOnly && $this->config['autoload_from_metadata']) { $metaDataSettings = IdPMetadataParser::parseRemoteXML($settings['idp']['entityId']); } $spSettings = $this->loadOneloginServiceProviderDetails(); $settings = array_replace_recursive($settings, $spSettings, $metaDataSettings, $overrides); - return new Auth($settings); + return new Auth($settings, $spOnly); } /** @@ -200,7 +213,7 @@ class Saml2Service protected function loadOneloginServiceProviderDetails(): array { $spDetails = [ - 'entityId' => url('/http/source.bookstackapp.com/saml2/metadata'), + 'entityId' => url('/http/source.bookstackapp.com/saml2/metadata'), 'assertionConsumerService' => [ 'url' => url('/http/source.bookstackapp.com/saml2/acs'), ], @@ -211,7 +224,7 @@ class Saml2Service return [ 'baseurl' => url('/http/source.bookstackapp.com/saml2'), - 'sp' => $spDetails, + 'sp' => $spDetails, ]; } @@ -263,6 +276,7 @@ class Saml2Service /** * 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 @@ -275,9 +289,9 @@ class Saml2Service return [ 'external_id' => $externalId, - 'name' => $this->getUserDisplayName($samlAttributes, $externalId), - 'email' => $email, - 'saml_id' => $samlID, + 'name' => $this->getUserDisplayName($samlAttributes, $externalId), + 'email' => $email, + 'saml_id' => $samlID, ]; } @@ -344,8 +358,8 @@ class Saml2Service 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, ]); } @@ -359,7 +373,9 @@ class Saml2Service } $user = $this->registrationService->findOrRegister( - $userDetails['name'], $userDetails['email'], $userDetails['external_id'] + $userDetails['name'], + $userDetails['email'], + $userDetails['external_id'] ); if ($user === null) {