1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-07-28 17:02:04 +03:00

Updated SAML ACS post to retain user session

Session was being lost due to the callback POST request cookies
not being provided due to samesite=lax. This instead adds an additional
hop in the flow to route the request via a GET request so the session is
retained. SAML POST data is stored encrypted in cache via a unique ID
then pulled out straight afterwards, and restored into POST for the SAML
toolkit to validate.

Updated testing to cover.
This commit is contained in:
Dan Brown
2021-10-20 13:30:45 +01:00
parent 859934d6a3
commit cdef1b3ab0
4 changed files with 144 additions and 95 deletions

View File

@ -4,6 +4,9 @@ namespace BookStack\Http\Controllers\Auth;
use BookStack\Auth\Access\Saml2Service;
use BookStack\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Str;
class Saml2Controller extends Controller
{
@ -68,17 +71,56 @@ class Saml2Controller extends Controller
}
/**
* Assertion Consumer Service.
* Processes the SAML response from the IDP.
* Assertion Consumer Service start URL. Takes the SAMLResponse from the IDP.
* Due to being an external POST request, we likely won't have context of the
* current user session due to lax cookies. To work around this we store the
* SAMLResponse data and redirect to the processAcs endpoint for the actual
* processing of the request with proper context of the user session.
*/
public function acs()
public function startAcs(Request $request)
{
$requestId = session()->pull('saml2_request_id', null);
// 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');
$user = $this->samlService->processAcsResponse($requestId);
if ($user === null) {
$samlResponse = $request->get('SAMLResponse', null);
if (empty($samlResponse)) {
$this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));
return redirect('/login');
}
$acsId = Str::random(16);
$cacheKey = 'saml2_acs:' . $acsId;
cache()->set($cacheKey, encrypt($samlResponse), 10);
return redirect()->guest('/saml2/acs?id=' . $acsId);
}
/**
* Assertion Consumer Service process endpoint.
* Processes the SAML response from the IDP with context of the current session.
* Takes the SAML request from the cache, added by the startAcs method above.
*/
public function processAcs(Request $request)
{
$acsId = $request->get('id', null);
$cacheKey = 'saml2_acs:' . $acsId;
$samlResponse = null;
try {
$samlResponse = decrypt(cache()->pull($cacheKey));
} catch (\Exception $exception) {}
$requestId = session()->pull('saml2_request_id', 'unset');
if (empty($acsId) || empty($samlResponse)) {
$this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));
return redirect('/login');
}
$user = $this->samlService->processAcsResponse($requestId, $samlResponse);
if (is_null($user)) {
$this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));
return redirect('/login');
}