Skip to content

Commit

Permalink
Changed logout routes to POST instead of GET
Browse files Browse the repository at this point in the history
As per #3047.

Also made some SAML specific fixes:
- IDP initiated login was broken due to forced default session value.
  Double checked against OneLogin lib docs that this reverted logic was fine.
- Changed how the saml login flow works to use 'withoutMiddleware' on
  the route instead of hacking out the session driver. This was due to
  the array driver (previously used for the hack) no longer being
  considered non-persistent.
  • Loading branch information
ssddanbrown committed Nov 14, 2021
1 parent fceb4ec commit f910738
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 27 deletions.
2 changes: 1 addition & 1 deletion app/Auth/Access/Saml2Service.php
Expand Up @@ -99,7 +99,7 @@ public function logout(User $user): array
* @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.
Expand Down
10 changes: 2 additions & 8 deletions app/Http/Controllers/Auth/Saml2Controller.php
Expand Up @@ -5,8 +5,7 @@
use BookStack\Auth\Access\Saml2Service;
use BookStack\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Str;
use Illuminate\Support\Str;

class Saml2Controller extends Controller
{
Expand Down Expand Up @@ -79,11 +78,6 @@ public function sls()
*/
public function startAcs(Request $request)
{
// Note: This is a bit of a hack to prevent a session being stored
// on the response of this request. Within Laravel7+ this could instead
// be done via removing the StartSession middleware from the route.
config()->set('session.driver', 'array');

$samlResponse = $request->get('SAMLResponse', null);

if (empty($samlResponse)) {
Expand Down Expand Up @@ -114,7 +108,7 @@ public function processAcs(Request $request)
$samlResponse = decrypt(cache()->pull($cacheKey));
} catch (\Exception $exception) {
}
$requestId = session()->pull('saml2_request_id', 'unset');
$requestId = session()->pull('saml2_request_id', null);

if (empty($acsId) || empty($samlResponse)) {
$this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));
Expand Down
12 changes: 7 additions & 5 deletions resources/views/common/header.blade.php
Expand Up @@ -71,11 +71,13 @@ class="mobile-menu-toggle hide-over-l">@icon('more')</button>
<a href="{{ $currentUser->getEditUrl() }}">@icon('edit'){{ trans('common.edit_profile') }}</a>
</li>
<li>
@if(config('auth.method') === 'saml2')
<a href="{{ url('/saml2/logout') }}">@icon('logout'){{ trans('auth.logout') }}</a>
@else
<a href="{{ url('/logout') }}">@icon('logout'){{ trans('auth.logout') }}</a>
@endif
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
method="post">
{{ csrf_field() }}
<button class="text-muted icon-list-item text-primary">
@icon('logout'){{ trans('auth.logout') }}
</button>
</form>
</li>
<li><hr></li>
<li>
Expand Down
10 changes: 7 additions & 3 deletions routes/web.php
Expand Up @@ -277,7 +277,7 @@
// Login/Logout routes
Route::get('/login', [Auth\LoginController::class, 'getLogin']);
Route::post('/login', [Auth\LoginController::class, 'login']);
Route::get('/logout', [Auth\LoginController::class, 'logout']);
Route::post('/logout', [Auth\LoginController::class, 'logout']);
Route::get('/register', [Auth\RegisterController::class, 'getRegister']);
Route::get('/register/confirm', [Auth\ConfirmEmailController::class, 'show']);
Route::get('/register/confirm/awaiting', [Auth\ConfirmEmailController::class, 'showAwaiting']);
Expand All @@ -287,10 +287,14 @@

// SAML routes
Route::post('/saml2/login', [Auth\Saml2Controller::class, 'login']);
Route::get('/saml2/logout', [Auth\Saml2Controller::class, 'logout']);
Route::post('/saml2/logout', [Auth\Saml2Controller::class, 'logout']);
Route::get('/saml2/metadata', [Auth\Saml2Controller::class, 'metadata']);
Route::get('/saml2/sls', [Auth\Saml2Controller::class, 'sls']);
Route::post('/saml2/acs', [Auth\Saml2Controller::class, 'startAcs']);
Route::post('/saml2/acs', [Auth\Saml2Controller::class, 'startAcs'])->withoutMiddleware([
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\BookStack\Http\Middleware\VerifyCsrfToken::class,
]);
Route::get('/saml2/acs', [Auth\Saml2Controller::class, 'processAcs']);

// OIDC routes
Expand Down
4 changes: 2 additions & 2 deletions tests/Auth/AuthTest.php
Expand Up @@ -192,7 +192,7 @@ public function test_restricted_registration_with_confirmation_disabled()
public function test_logout()
{
$this->asAdmin()->get('/')->assertOk();
$this->get('/logout')->assertRedirect('/');
$this->post('/logout')->assertRedirect('/');
$this->get('/')->assertRedirect('/login');
}

Expand All @@ -204,7 +204,7 @@ public function test_mfa_session_cleared_on_logout()
$mfaSession->markVerifiedForUser($user);
$this->assertTrue($mfaSession->isVerifiedForUser($user));

$this->asAdmin()->get('/logout');
$this->asAdmin()->post('/logout');
$this->assertFalse($mfaSession->isVerifiedForUser($user));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Auth/OidcTest.php
Expand Up @@ -90,7 +90,7 @@ public function test_standard_login_routes_inaccessible()
public function test_logout_route_functions()
{
$this->actingAs($this->getEditor());
$this->get('/logout');
$this->post('/logout');
$this->assertFalse(auth()->check());
}

Expand Down
13 changes: 6 additions & 7 deletions tests/Auth/Saml2Test.php
Expand Up @@ -157,8 +157,7 @@ public function test_logout_link_directs_to_saml_path()
]);

$resp = $this->actingAs($this->getEditor())->get('/');
$resp->assertElementExists('a[href$="/saml2/logout"]');
$resp->assertElementContains('a[href$="/saml2/logout"]', 'Logout');
$resp->assertElementContains('form[action$="/saml2/logout"] button', 'Logout');
}

public function test_logout_sls_flow()
Expand All @@ -177,7 +176,7 @@ public function test_logout_sls_flow()

$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);

$req = $this->get('/saml2/logout');
$req = $this->post('/saml2/logout');
$redirect = $req->headers->get('location');
$this->assertStringStartsWith('http://saml.local/saml2/idp/SingleLogoutService.php', $redirect);
$this->withGet(['SAMLResponse' => $this->sloResponseData], $handleLogoutResponse);
Expand All @@ -193,7 +192,7 @@ public function test_logout_sls_flow_when_sls_not_configured()
$this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
$this->assertTrue($this->isAuthenticated());

$req = $this->get('/saml2/logout');
$req = $this->post('/saml2/logout');
$req->assertRedirect('/');
$this->assertFalse($this->isAuthenticated());
}
Expand All @@ -216,13 +215,13 @@ public function test_dump_user_details_option_works()
public function test_saml_routes_are_only_active_if_saml_enabled()
{
config()->set(['auth.method' => 'standard']);
$getRoutes = ['/logout', '/metadata', '/sls'];
$getRoutes = ['/metadata', '/sls'];
foreach ($getRoutes as $route) {
$req = $this->get('/saml2' . $route);
$this->assertPermissionError($req);
}

$postRoutes = ['/login', '/acs'];
$postRoutes = ['/login', '/acs', '/logout'];
foreach ($postRoutes as $route) {
$req = $this->post('/saml2' . $route);
$this->assertPermissionError($req);
Expand All @@ -249,7 +248,7 @@ public function test_standard_login_routes_inaccessible()
$resp = $this->post('/login');
$this->assertPermissionError($resp);

$resp = $this->get('/logout');
$resp = $this->post('/logout');
$this->assertPermissionError($resp);
}

Expand Down

0 comments on commit f910738

Please sign in to comment.