Skip to content

Commit

Permalink
SAML support for OKTA (#1963)
Browse files Browse the repository at this point in the history
  • Loading branch information
dasgarner authored Aug 4, 2023
1 parent da16ca0 commit 8a77268
Showing 1 changed file with 45 additions and 25 deletions.
70 changes: 45 additions & 25 deletions lib/Middleware/SAMLAuthentication.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
Expand All @@ -27,9 +27,8 @@
use OneLogin\Saml2\Error;
use OneLogin\Saml2\Settings;
use OneLogin\Saml2\Utils;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Routing\RouteContext;
use Slim\Http\Response as Response;
use Slim\Http\ServerRequest as Request;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\LogoutTrait;
use Xibo\Helper\Random;
Expand All @@ -55,7 +54,7 @@ public function addRoutes()
$app->getContainer()->logoutRoute = 'saml.logout';

// Route providing SAML metadata
$app->get('/saml/metadata', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) {
$app->get('/saml/metadata', function (Request $request, Response $response) {
$settings = new Settings($this->getConfig()->samlSettings, true);
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
Expand All @@ -72,19 +71,19 @@ public function addRoutes()
});

// SAML Login
$app->get('/saml/login', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) {
$app->get('/saml/login', function (Request $request, Response $response) {
// Initiate SAML SSO
$auth = new Auth($this->getConfig()->samlSettings);
return $auth->login();
});

// SAML Logout
$app->get('/saml/logout', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) {
$app->get('/saml/logout', function (Request $request, Response $response) {
return $this->samlLogout($request, $response);
})->setName('saml.logout');

// SAML Assertion Consumer Endpoint
$app->post('/saml/acs', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) {
$app->post('/saml/acs', function (Request $request, Response $response) {
// Log some interesting things
$this->getLog()->debug('Arrived at the ACS route with own URL: ' . Utils::getSelfRoutedURLNoQuery());

Expand Down Expand Up @@ -278,39 +277,49 @@ public function addRoutes()
]);
}

// Redirect back to the originally-requested url
// Redirect back to the originally-requested url, if provided
// it is not clear why basename is used here, it seems to be something to do with a logout loop
$params = $request->getParams();
$redirect = !isset($params['RelayState']) || (isset($params['RelayState']) && basename($params['RelayState']) == 'login')
$relayState = $params['RelayState'] ?? null;
$redirect = empty($relayState) || basename($relayState) === 'login'
? $this->getRouteParser()->urlFor('home')
: $params['RelayState'];
: $relayState;

return $response->withRedirect($redirect);
}
});

// Single Logout Service
$app->get('/saml/sls', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) use ($app) {

$app->map(['GET', 'POST'], '/saml/sls', function (Request $request, Response $response) use ($app) {
// Make request to IDP
$auth = new Auth($app->getContainer()->get('configService')->samlSettings);
$auth->processSLO(false, null, false, function () use ($request) {
$this->completeLogoutFlow($this->getUser($_SESSION['userid'], $request->getAttribute('ip_address')), $this->getSession(), $this->getLog(), $request);
});
try {
$auth->processSLO(false, null, false, function () use ($request) {
// Audit that the IDP has completed this request.
$this->getLog()->setIpAddress($request->getAttribute('ip_address'));
$this->getLog()->audit('User', 0, 'Idp SLO completed', [
'UserAgent' => $request->getHeader('User-Agent')
]);
});
} catch (\Exception $e) {
// Ignored - get with getErrors()
}

$errors = $auth->getErrors();

if (empty($errors)) {
return $response->withRedirect($this->getRouteParser()->urlFor('home'));
} else {
throw new AccessDeniedException("SLO failed. " . implode(', ', $errors));
throw new AccessDeniedException('SLO failed. ' . implode(', ', $errors));
}
});

return $this;
}

/**
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param \Psr\Http\Message\ResponseInterface $response
* @param \Slim\Http\ServerRequest $request
* @param \Slim\Http\Response $response
* @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response
* @throws \OneLogin\Saml2\Error
*/
Expand All @@ -322,6 +331,14 @@ public function samlLogout(Request $request, Response $response)
&& isset($samlSettings['workflow']['slo'])
&& $samlSettings['workflow']['slo'] == true
) {
// Complete our own logout flow
$this->completeLogoutFlow(
$this->getUser($_SESSION['userid'], $request->getAttribute('ip_address')),
$this->getSession(),
$this->getLog(),
$request
);

// Initiate SAML SLO
$auth = new Auth($samlSettings);
return $response->withRedirect($auth->logout());
Expand All @@ -335,7 +352,7 @@ public function samlLogout(Request $request, Response $response)
* @return Response
* @throws \OneLogin\Saml2\Error
*/
public function redirectToLogin(Request $request)
public function redirectToLogin(\Psr\Http\Message\ServerRequestInterface $request)
{
if ($this->isAjax($request)) {
return $this->createResponse($request)->withJson(ApplicationState::asRequiresLogin());
Expand All @@ -347,7 +364,7 @@ public function redirectToLogin(Request $request)
}

/** @inheritDoc */
public function getPublicRoutes(Request $request)
public function getPublicRoutes(\Psr\Http\Message\ServerRequestInterface $request)
{
return array_merge($request->getAttribute('publicRoutes', []), [
'/saml/metadata',
Expand All @@ -367,8 +384,11 @@ public function shouldRedirectPublicRoute($route)
}

/** @inheritDoc */
public function addToRequest(Request $request)
public function addToRequest(\Psr\Http\Message\ServerRequestInterface $request)
{
return $request->withAttribute('excludedCsrfRoutes', array_merge($request->getAttribute('excludedCsrfRoutes', []), ['/saml/acs']));
return $request->withAttribute(
'excludedCsrfRoutes',
array_merge($request->getAttribute('excludedCsrfRoutes', []), ['/saml/acs', '/saml/sls'])
);
}
}

0 comments on commit 8a77268

Please sign in to comment.