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;
*/
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,
*
* @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) {
* @throws JsonDebugException
* @throws UserRegistrationException
*/
- public function processAcsResponse(string $requestId, string $samlResponse): ?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.
$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()) {
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)) {
*/
public function metadata(): string
{
- $toolKit = $this->getToolkit();
+ $toolKit = $this->getToolkit(true);
$settings = $toolKit->getSettings();
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
* @throws Error
* @throws Exception
*/
- protected function getToolkit(): Auth
+ protected function getToolkit(bool $spOnly = false): Auth
{
$settings = $this->config['onelogin'];
$overrides = $this->config['onelogin_overrides'] ?? [];
}
$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);
}
/**