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
{
-
protected $samlService;
/**
*/
public function __construct(Saml2Service $samlService)
{
- parent::__construct();
$this->samlService = $samlService;
-
- // SAML2 access middleware
- $this->middleware(function ($request, $next) {
- if (!config('saml2.enabled')) {
- $this->showPermissionError();
- }
-
- return $next($request);
- });
+ $this->middleware('guard:saml2');
}
/**
*/
public function logout()
{
- $logoutDetails = $this->samlService->logout();
+ $logoutDetails = $this->samlService->logout(auth()->user());
if ($logoutDetails['id']) {
session()->flash('saml2_logout_request_id', $logoutDetails['id']);
public function metadata()
{
$metaData = $this->samlService->metadata();
+
return response()->make($metaData, 200, [
- 'Content-Type' => 'text/xml'
+ 'Content-Type' => 'text/xml',
]);
}
{
$requestId = session()->pull('saml2_logout_request_id', null);
$redirect = $this->samlService->processSlsResponse($requestId) ?? '/';
+
return redirect($redirect);
}
/**
- * 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');
+
+ $samlResponse = $request->get('SAMLResponse', null);
- $user = $this->samlService->processAcsResponse($requestId);
- if ($user === null) {
+ if (empty($samlResponse)) {
$this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));
+
return redirect('/login');
}
- session()->put('last_login_type', 'saml2');
- return redirect()->intended();
+ $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');
+ }
+
+ return redirect()->intended();
+ }
}