From aef6eb81e4789f97c7ff23b87295e239c0aead14 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 17 Nov 2019 15:40:36 +0000 Subject: [PATCH] Added SAML singleLogoutService capabilities --- app/Auth/Access/Saml2Service.php | 73 +++++++++++++++++-- app/Http/Controllers/Auth/LoginController.php | 19 +++++ app/Http/Controllers/Auth/Saml2Controller.php | 19 ++++- routes/web.php | 3 +- 4 files changed, 103 insertions(+), 11 deletions(-) diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index a5ca54c8d..a9441dc40 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -4,10 +4,12 @@ use BookStack\Auth\User; use BookStack\Auth\UserRepo; use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\SamlException; +use Exception; use Illuminate\Support\Str; use OneLogin\Saml2\Auth; use OneLogin\Saml2\Error; use OneLogin\Saml2\IdPMetadataParser; +use OneLogin\Saml2\ValidationError; /** * Class Saml2Service @@ -33,7 +35,7 @@ class Saml2Service extends ExternalAuthService /** * Initiate a login flow. - * @throws \OneLogin\Saml2\Error + * @throws Error */ public function login(): array { @@ -45,25 +47,50 @@ class Saml2Service extends ExternalAuthService ]; } + /** + * Initiate a logout flow. + * @throws Error + */ + public function logout(): array + { + $toolKit = $this->getToolkit(); + $returnRoute = url('/'); + + try { + $url = $toolKit->logout($returnRoute, [], null, null, true); + $id = $toolKit->getLastRequestID(); + } catch (Error $error) { + if ($error->getCode() !== Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED) { + throw $error; + } + + $this->actionLogout(); + $url = '/'; + $id = null; + } + + return ['url' => $url, 'id' => $id]; + } + /** * Process the ACS response from the idp and return the * matching, or new if registration active, user matched to the idp. * Returns null if not authenticated. * @throws Error * @throws SamlException - * @throws \OneLogin\Saml2\ValidationError + * @throws ValidationError * @throws JsonDebugException */ public function processAcsResponse(?string $requestId): ?User { - $toolkit = $this->getToolkit(); - $toolkit->processResponse($requestId); - $errors = $toolkit->getErrors(); - if (is_null($requestId)) { throw new SamlException(trans('errors.saml_invalid_response_id')); } + $toolkit = $this->getToolkit(); + $toolkit->processResponse($requestId); + $errors = $toolkit->getErrors(); + if (!empty($errors)) { throw new Error( 'Invalid ACS Response: '.implode(', ', $errors) @@ -80,6 +107,36 @@ class Saml2Service extends ExternalAuthService return $this->processLoginCallback($id, $attrs); } + /** + * Process a response for the single logout service. + * @throws Error + */ + public function processSlsResponse(?string $requestId): ?string + { + $toolkit = $this->getToolkit(); + $redirect = $toolkit->processSLO(true, $requestId, false, null, true); + + $errors = $toolkit->getErrors(); + + if (!empty($errors)) { + throw new Error( + 'Invalid SLS Response: '.implode(', ', $errors) + ); + } + + $this->actionLogout(); + return $redirect; + } + + /** + * Do the required actions to log a user out. + */ + protected function actionLogout() + { + auth()->logout(); + session()->invalidate(); + } + /** * Get the metadata for this service provider. * @throws Error @@ -103,8 +160,8 @@ class Saml2Service extends ExternalAuthService /** * Load the underlying Onelogin SAML2 toolkit. - * @throws \OneLogin\Saml2\Error - * @throws \Exception + * @throws Error + * @throws Exception */ protected function getToolkit(): Auth { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 477d3d26b..b1d22d57e 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -146,4 +146,23 @@ class LoginController extends Controller session()->put('social-callback', 'login'); return $this->socialAuthService->startLogIn($socialDriver); } + + /** + * Log the user out of the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function logout(Request $request) + { + if (config('saml2.enabled') && session()->get('last_login_type') === 'saml2') { + return redirect('/saml2/logout'); + } + + $this->guard()->logout(); + + $request->session()->invalidate(); + + return $this->loggedOut($request) ?: redirect('/'); + } } diff --git a/app/Http/Controllers/Auth/Saml2Controller.php b/app/Http/Controllers/Auth/Saml2Controller.php index d54e925bb..c32f19c5e 100644 --- a/app/Http/Controllers/Auth/Saml2Controller.php +++ b/app/Http/Controllers/Auth/Saml2Controller.php @@ -31,6 +31,20 @@ class Saml2Controller extends Controller return redirect($loginDetails['url']); } + /** + * Start the logout flow via SAML2. + */ + public function logout() + { + $logoutDetails = $this->samlService->logout(); + + if ($logoutDetails['id']) { + session()->flash('saml2_logout_request_id', $logoutDetails['id']); + } + + return redirect($logoutDetails['url']); + } + /* * Get the metadata for this SAML2 service provider. */ @@ -48,7 +62,9 @@ class Saml2Controller extends Controller */ public function sls() { - // TODO + $requestId = session()->pull('saml2_logout_request_id', null); + $redirect = $this->samlService->processSlsResponse($requestId) ?? '/'; + return redirect($redirect); } /** @@ -65,6 +81,7 @@ class Saml2Controller extends Controller return redirect('/login'); } + session()->put('last_login_type', 'saml2'); return redirect()->intended(); } diff --git a/routes/web.php b/routes/web.php index 461d3c1aa..0c554bf8e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -219,8 +219,7 @@ Route::post('/register', 'Auth\RegisterController@postRegister'); // SAML routes // TODO - Prevent access without SAML2 enabled via middleware Route::get('/saml2/login', 'Auth\Saml2Controller@login'); -// TODO - Handle logout? -// Route::get('/saml2/logout', 'Auth\Saml2Controller@logout'); +Route::get('/saml2/logout', 'Auth\Saml2Controller@logout'); Route::get('/saml2/metadata', 'Auth\Saml2Controller@metadata'); Route::get('/saml2/sls', 'Auth\Saml2Controller@sls'); Route::post('/saml2/acs', 'Auth\Saml2Controller@acs');