-
Thijs Kinkhorst authoredThijs Kinkhorst authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
SP.php 28.49 KiB
<?php
namespace SimpleSAML\Module\saml\Auth\Source;
use SimpleSAML\Auth\Source;
use SimpleSAML\Auth\State;
class SP extends Source
{
/**
* The entity ID of this SP.
*
* @var string
*/
private $entityId;
/**
* The metadata of this SP.
*
* @var \SimpleSAML\Configuration
*/
private $metadata;
/**
* The IdP the user is allowed to log into.
*
* @var string|null The IdP the user can log into, or null if the user can log into all IdPs.
*/
private $idp;
/**
* URL to discovery service.
*
* @var string|null
*/
private $discoURL;
/**
* Constructor for SAML SP authentication source.
*
* @param array $info Information about this authentication source.
* @param array $config Configuration.
*/
public function __construct($info, $config)
{
assert(is_array($info));
assert(is_array($config));
// Call the parent constructor first, as required by the interface
parent::__construct($info, $config);
if (!isset($config['entityID'])) {
$config['entityID'] = $this->getMetadataURL();
}
/* For compatibility with code that assumes that $metadata->getString('entityid')
* gives the entity id. */
$config['entityid'] = $config['entityID'];
$this->metadata = \SimpleSAML\Configuration::loadFromArray(
$config,
'authsources['.var_export($this->authId, true).']'
);
$this->entityId = $this->metadata->getString('entityID');
$this->idp = $this->metadata->getString('idp', null);
$this->discoURL = $this->metadata->getString('discoURL', null);
if (empty($this->discoURL) && \SimpleSAML\Module::isModuleEnabled('discojuice')) {
$this->discoURL = \SimpleSAML\Module::getModuleURL('discojuice/central.php');
}
}
/**
* Retrieve the URL to the metadata of this SP.
*
* @return string The metadata URL.
*/
public function getMetadataURL()
{
return \SimpleSAML\Module::getModuleURL('saml/sp/metadata.php/'.urlencode($this->authId));
}
/**
* Retrieve the entity id of this SP.
*
* @return string The entity id of this SP.
*/
public function getEntityId()
{
return $this->entityId;
}
/**
* Retrieve the metadata of this SP.
*
* @return \SimpleSAML\Configuration The metadata of this SP.
*/
public function getMetadata()
{
return $this->metadata;
}
/**
* Retrieve the metadata of an IdP.
*
* @param string $entityId The entity id of the IdP.
* @return \SimpleSAML\Configuration The metadata of the IdP.
*/
public function getIdPMetadata($entityId)
{
assert(is_string($entityId));
if ($this->idp !== null && $this->idp !== $entityId) {
throw new \SimpleSAML\Error\Exception('Cannot retrieve metadata for IdP '.
var_export($entityId, true).' because it isn\'t a valid IdP for this SP.');
}
$metadataHandler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
// First, look in saml20-idp-remote.
try {
return $metadataHandler->getMetaDataConfig($entityId, 'saml20-idp-remote');
} catch (\Exception $e) {
// Metadata wasn't found
\SimpleSAML\Logger::debug('getIdpMetadata: '.$e->getMessage());
}
// Not found in saml20-idp-remote, look in shib13-idp-remote
try {
return $metadataHandler->getMetaDataConfig($entityId, 'shib13-idp-remote');
} catch (\Exception $e) {
// Metadata wasn't found
\SimpleSAML\Logger::debug('getIdpMetadata: '.$e->getMessage());
}
// Not found
throw new \SimpleSAML\Error\Exception('Could not find the metadata of an IdP with entity ID '.
var_export($entityId, true));
}
/**
* Send a SAML1 SSO request to an IdP.
*
* @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP.
* @param array $state The state array for the current authentication.
*/
private function startSSO1(\SimpleSAML\Configuration $idpMetadata, array $state)
{
$idpEntityId = $idpMetadata->getString('entityid');
$state['saml:idp'] = $idpEntityId;
$ar = new \SimpleSAML\XML\Shib13\AuthnRequest();
$ar->setIssuer($this->entityId);
$id = State::saveState($state, 'saml:sp:sso');
$ar->setRelayState($id);
$useArtifact = $idpMetadata->getBoolean('saml1.useartifact', null);
if ($useArtifact === null) {
$useArtifact = $this->metadata->getBoolean('saml1.useartifact', false);
}
if ($useArtifact) {
$shire = \SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/'.$this->authId.'/artifact');
} else {
$shire = \SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/'.$this->authId);
}
$url = $ar->createRedirect($idpEntityId, $shire);
\SimpleSAML\Logger::debug('Starting SAML 1 SSO to '.var_export($idpEntityId, true).
' from '.var_export($this->entityId, true).'.');
\SimpleSAML\Utils\HTTP::redirectTrustedURL($url);
}
/**
* Send a SAML2 SSO request to an IdP
*
* @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP.
* @param array $state The state array for the current authentication.
*/
private function startSSO2(\SimpleSAML\Configuration $idpMetadata, array $state)
{
if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) {
State::throwException(
$state,
new \SimpleSAML\Module\saml\Error\ProxyCountExceeded(\SAML2\Constants::STATUS_RESPONDER)
);
}
$ar = \SimpleSAML\Module\saml\Message::buildAuthnRequest($this->metadata, $idpMetadata);
$ar->setAssertionConsumerServiceURL(\SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->authId));
if (isset($state['\SimpleSAML\Auth\Source.ReturnURL'])) {
$ar->setRelayState($state['\SimpleSAML\Auth\Source.ReturnURL']);
}
if (isset($state['saml:AuthnContextClassRef'])) {
$accr = \SimpleSAML\Utils\Arrays::arrayize($state['saml:AuthnContextClassRef']);
$comp = \SAML2\Constants::COMPARISON_EXACT;
if (isset($state['saml:AuthnContextComparison'])
&& in_array($state['AuthnContextComparison'], [
\SAML2\Constants::COMPARISON_EXACT,
\SAML2\Constants::COMPARISON_MINIMUM,
\SAML2\Constants::COMPARISON_MAXIMUM,
\SAML2\Constants::COMPARISON_BETTER,
], true)) {
$comp = $state['saml:AuthnContextComparison'];
}
$ar->setRequestedAuthnContext(['AuthnContextClassRef' => $accr, 'Comparison' => $comp]);
}
if (isset($state['ForceAuthn'])) {
$ar->setForceAuthn((bool) $state['ForceAuthn']);
}
if (isset($state['isPassive'])) {
$ar->setIsPassive((bool) $state['isPassive']);
}
if (isset($state['saml:NameID'])) {
if (!is_array($state['saml:NameID']) && !is_a($state['saml:NameID'], '\SAML2\XML\saml\NameID')) {
throw new \SimpleSAML\Error\Exception('Invalid value of $state[\'saml:NameID\'].');
}
$ar->setNameId($state['saml:NameID']);
}
if (isset($state['saml:NameIDPolicy'])) {
$policy = null;
if (is_string($state['saml:NameIDPolicy'])) {
$policy = [
'Format' => (string) $state['saml:NameIDPolicy'],
'AllowCreate' => true,
];
} elseif (is_array($state['saml:NameIDPolicy'])) {
$policy = $state['saml:NameIDPolicy'];
} elseif ($state['saml:NameIDPolicy'] === null) {
$policy = ['Format' => \SAML2\Constants::NAMEID_TRANSIENT];
}
if ($policy !== null) {
$ar->setNameIdPolicy($policy);
}
}
if (isset($state['saml:IDPList'])) {
$IDPList = $state['saml:IDPList'];
} else {
$IDPList = [];
}
$ar->setIDPList(
array_unique(
array_merge(
$this->metadata->getArray('IDPList', []),
$idpMetadata->getArray('IDPList', []),
(array) $IDPList
)
)
);
if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) {
$ar->setProxyCount($state['saml:ProxyCount']);
} elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) {
$ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null));
} elseif ($this->metadata->getInteger('ProxyCount', null) !== null) {
$ar->setProxyCount($this->metadata->getInteger('ProxyCount', null));
}
$requesterID = [];
if (isset($state['saml:RequesterID'])) {
$requesterID = $state['saml:RequesterID'];
}
if (isset($state['core:SP'])) {
$requesterID[] = $state['core:SP'];
}
$ar->setRequesterID($requesterID);
if (isset($state['saml:Extensions'])) {
$ar->setExtensions($state['saml:Extensions']);
}
// save IdP entity ID as part of the state
$state['ExpectedIssuer'] = $idpMetadata->getString('entityid');
$id = State::saveState($state, 'saml:sp:sso', true);
$ar->setId($id);
\SimpleSAML\Logger::debug(
'Sending SAML 2 AuthnRequest to '.var_export($idpMetadata->getString('entityid'), true)
);
// Select appropriate SSO endpoint
if ($ar->getProtocolBinding() === \SAML2\Constants::BINDING_HOK_SSO) {
$dst = $idpMetadata->getDefaultEndpoint(
'SingleSignOnService',
[
\SAML2\Constants::BINDING_HOK_SSO
]
);
} else {
$dst = $idpMetadata->getEndpointPrioritizedByBinding(
'SingleSignOnService',
[
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST,
]
);
}
$ar->setDestination($dst['Location']);
$b = \SAML2\Binding::getBinding($dst['Binding']);
$this->sendSAML2AuthnRequest($state, $b, $ar);
assert(false);
}
/**
* Function to actually send the authentication request.
*
* This function does not return.
*
* @param array &$state The state array.
* @param \SAML2\Binding $binding The binding.
* @param \SAML2\AuthnRequest $ar The authentication request.
*/
public function sendSAML2AuthnRequest(array &$state, \SAML2\Binding $binding, \SAML2\AuthnRequest $ar)
{
$binding->send($ar);
assert(false);
}
/**
* Send a SSO request to an IdP.
*
* @param string $idp The entity ID of the IdP.
* @param array $state The state array for the current authentication.
*/
public function startSSO($idp, array $state)
{
assert(is_string($idp));
$idpMetadata = $this->getIdPMetadata($idp);
$type = $idpMetadata->getString('metadata-set');
switch ($type) {
case 'shib13-idp-remote':
$this->startSSO1($idpMetadata, $state);
assert(false); // Should not return
case 'saml20-idp-remote':
$this->startSSO2($idpMetadata, $state);
assert(false); // Should not return
default:
// Should only be one of the known types
assert(false);
}
}
/**
* Start an IdP discovery service operation.
*
* @param array $state The state array.
*/
private function startDisco(array $state)
{
$id = State::saveState($state, 'saml:sp:sso');
$discoURL = $this->discoURL;
if ($discoURL === null) {
// Fallback to internal discovery service
$discoURL = \SimpleSAML\Module::getModuleURL('saml/disco.php');
}
$returnTo = \SimpleSAML\Module::getModuleURL('saml/sp/discoresp.php', ['AuthID' => $id]);
$params = [
'entityID' => $this->entityId,
'return' => $returnTo,
'returnIDParam' => 'idpentityid'
];
if (isset($state['saml:IDPList'])) {
$params['IDPList'] = $state['saml:IDPList'];
}
if (isset($state['isPassive']) && $state['isPassive']) {
$params['isPassive'] = 'true';
}
\SimpleSAML\Utils\HTTP::redirectTrustedURL($discoURL, $params);
}
/**
* Start login.
*
* This function saves the information about the login, and redirects to the IdP.
*
* @param array &$state Information about the current authentication.
*/
public function authenticate(&$state)
{
assert(is_array($state));
// We are going to need the authId in order to retrieve this authentication source later
$state['saml:sp:AuthId'] = $this->authId;
$idp = $this->idp;
if (isset($state['saml:idp'])) {
$idp = (string) $state['saml:idp'];
}
if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) {
// we have a SAML IDPList (we are a proxy): filter the list of IdPs available
$mdh = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
$known_idps = $mdh->getList();
$intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
if (empty($intersection)) {
// all requested IdPs are unknown
throw new \SimpleSAML\Module\saml\Error\NoSupportedIDP(
\SAML2\Constants::STATUS_REQUESTER,
'None of the IdPs requested are supported by this proxy.'
);
}
if (!is_null($idp) && !in_array($idp, $intersection, true)) {
// the IdP is enforced but not in the IDPList
throw new \SimpleSAML\Module\saml\Error\NoAvailableIDP(
\SAML2\Constants::STATUS_REQUESTER,
'None of the IdPs requested are available to this proxy.'
);
}
if (is_null($idp) && sizeof($intersection) === 1) {
// only one IdP requested or valid
$idp = current($state['saml:IDPList']);
}
}
if ($idp === null) {
$this->startDisco($state);
assert(false);
}
$this->startSSO($idp, $state);
assert(false);
}
/**
* Re-authenticate an user.
*
* This function is called by the IdP to give the authentication source a chance to
* interact with the user even in the case when the user is already authenticated.
*
* @param array &$state Information about the current authentication.
*/
public function reauthenticate(array &$state)
{
assert(is_array($state));
$session = \SimpleSAML\Session::getSessionFromRequest();
$data = $session->getAuthState($this->authId);
foreach ($data as $k => $v) {
$state[$k] = $v;
}
// check if we have an IDPList specified in the request
if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0 &&
!in_array($state['saml:sp:IdP'], $state['saml:IDPList'], true)) {
/*
* The user has an existing, valid session. However, the SP
* provided a list of IdPs it accepts for authentication, and
* the IdP the existing session is related to is not in that list.
*
* First, check if we recognize any of the IdPs requested.
*/
$mdh = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
$known_idps = $mdh->getList();
$intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
if (empty($intersection)) {
// all requested IdPs are unknown
throw new \SimpleSAML\Module\saml\Error\NoSupportedIDP(
\SAML2\Constants::STATUS_REQUESTER,
'None of the IdPs requested are supported by this proxy.'
);
}
/*
* We have at least one IdP in the IDPList that we recognize, and
* it's not the one currently in use. Let's see if this proxy
* enforces the use of one single IdP.
*/
if (!is_null($this->idp) && !in_array($this->idp, $intersection, true)) {
// an IdP is enforced but not requested
throw new \SimpleSAML\Module\saml\Error\NoAvailableIDP(
\SAML2\Constants::STATUS_REQUESTER,
'None of the IdPs requested are available to this proxy.'
);
}
/*
* We need to inform the user, and ask whether we should logout before
* starting the authentication process again with a different IdP, or
* cancel the current SSO attempt.
*/
\SimpleSAML\Logger::warning(
"Reauthentication after logout is needed. The IdP '${state['saml:sp:IdP']}' is not in the IDPList ".
"provided by the Service Provider '${state['core:SP']}'."
);
$state['saml:sp:IdPMetadata'] = $this->getIdPMetadata($state['saml:sp:IdP']);
$state['saml:sp:AuthId'] = $this->authId;
self::askForIdPChange($state);
}
}
/**
* Ask the user to log out before being able to log in again with a
* different identity provider. Note that this method is intended for
* instances of SimpleSAMLphp running as a SAML proxy, and therefore
* acting both as an SP and an IdP at the same time.
*
* This method will never return.
*
* @param array $state The state array.
* The following keys must be defined in the array:
* - 'saml:sp:IdPMetadata': a \SimpleSAML\Configuration object containing
* the metadata of the IdP that authenticated the user in the current
* session.
* - 'saml:sp:AuthId': the identifier of the current authentication source.
* - 'core:IdP': the identifier of the local IdP.
* - 'SPMetadata': an array with the metadata of this local SP.
*
* @throws \SimpleSAML\Error\NoPassive In case the authentication request was passive.
*/
public static function askForIdPChange(array &$state)
{
assert(array_key_exists('saml:sp:IdPMetadata', $state));
assert(array_key_exists('saml:sp:AuthId', $state));
assert(array_key_exists('core:IdP', $state));
assert(array_key_exists('SPMetadata', $state));
if (isset($state['isPassive']) && (bool) $state['isPassive']) {
// passive request, we cannot authenticate the user
throw new \SimpleSAML\Module\saml\Error\NoPassive(
\SAML2\Constants::STATUS_REQUESTER,
'Reauthentication required'
);
}
// save the state WITHOUT a restart URL, so that we don't try an IdP-initiated login if something goes wrong
$id = State::saveState($state, 'saml:proxy:invalid_idp', true);
$url = \SimpleSAML\Module::getModuleURL('saml/proxy/invalid_session.php');
\SimpleSAML\Utils\HTTP::redirectTrustedURL($url, ['AuthState' => $id]);
assert(false);
}
/**
* Log the user out before logging in again.
*
* This method will never return.
*
* @param array $state The state array.
*/
public static function reauthLogout(array $state)
{
\SimpleSAML\Logger::debug('Proxy: logging the user out before re-authentication.');
if (isset($state['Responder'])) {
$state['saml:proxy:reauthLogout:PrevResponder'] = $state['Responder'];
}
$state['Responder'] = ['\SimpleSAML\Module\saml\Auth\Source\SP', 'reauthPostLogout'];
$idp = \SimpleSAML\IdP::getByState($state);
$idp->handleLogoutRequest($state, null);
assert(false);
}
/**
* Complete login operation after re-authenticating the user on another IdP.
*
* @param array $state The authentication state.
*/
public static function reauthPostLogin(array $state)
{
assert(isset($state['ReturnCallback']));
// Update session state
$session = \SimpleSAML\Session::getSessionFromRequest();
$authId = $state['saml:sp:AuthId'];
$session->doLogin($authId, State::getPersistentAuthData($state));
// resume the login process
call_user_func($state['ReturnCallback'], $state);
assert(false);
}
/**
* Post-logout handler for re-authentication.
*
* This method will never return.
*
* @param \SimpleSAML\IdP $idp The IdP we are logging out from.
* @param array &$state The state array with the state during logout.
*/
public static function reauthPostLogout(\SimpleSAML\IdP $idp, array $state)
{
assert(isset($state['saml:sp:AuthId']));
\SimpleSAML\Logger::debug('Proxy: logout completed.');
if (isset($state['saml:proxy:reauthLogout:PrevResponder'])) {
$state['Responder'] = $state['saml:proxy:reauthLogout:PrevResponder'];
}
$sp = Source::getById($state['saml:sp:AuthId'], '\SimpleSAML\Module\saml\Auth\Source\SP');
/** @var \SimpleSAML\Module\saml\Auth\Source\SP $authSource */
\SimpleSAML\Logger::debug('Proxy: logging in again.');
$sp->authenticate($state);
assert(false);
}
/**
* Start a SAML 2 logout operation.
*
* @param array $state The logout state.
*/
public function startSLO2(&$state)
{
assert(is_array($state));
assert(array_key_exists('saml:logout:IdP', $state));
assert(array_key_exists('saml:logout:NameID', $state));
assert(array_key_exists('saml:logout:SessionIndex', $state));
$id = State::saveState($state, 'saml:slosent');
$idp = $state['saml:logout:IdP'];
$nameId = $state['saml:logout:NameID'];
$sessionIndex = $state['saml:logout:SessionIndex'];
$idpMetadata = $this->getIdPMetadata($idp);
$endpoint = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', [
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST], false);
if ($endpoint === false) {
\SimpleSAML\Logger::info('No logout endpoint for IdP '.var_export($idp, true).'.');
return;
}
$lr = \SimpleSAML\Module\saml\Message::buildLogoutRequest($this->metadata, $idpMetadata);
$lr->setNameId($nameId);
$lr->setSessionIndex($sessionIndex);
$lr->setRelayState($id);
$lr->setDestination($endpoint['Location']);
$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', null);
if ($encryptNameId === null) {
$encryptNameId = $this->metadata->getBoolean('nameid.encryption', false);
}
if ($encryptNameId) {
$lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($idpMetadata));
}
$b = \SAML2\Binding::getBinding($endpoint['Binding']);
$b->send($lr);
assert(false);
}
/**
* Start logout operation.
*
* @param array $state The logout state.
*/
public function logout(&$state)
{
assert(is_array($state));
assert(array_key_exists('saml:logout:Type', $state));
$logoutType = $state['saml:logout:Type'];
switch ($logoutType) {
case 'saml1':
// Nothing to do
return;
case 'saml2':
$this->startSLO2($state);
return;
default:
// Should never happen
assert(false);
}
}
/**
* Handle a response from a SSO operation.
*
* @param array $state The authentication state.
* @param string $idp The entity id of the IdP.
* @param array $attributes The attributes.
*/
public function handleResponse(array $state, $idp, array $attributes)
{
assert(is_string($idp));
assert(array_key_exists('LogoutState', $state));
assert(array_key_exists('saml:logout:Type', $state['LogoutState']));
$idpMetadata = $this->getIdPMetadata($idp);
$spMetadataArray = $this->metadata->toArray();
$idpMetadataArray = $idpMetadata->toArray();
/* Save the IdP in the state array. */
$state['saml:sp:IdP'] = $idp;
$state['PersistentAuthData'][] = 'saml:sp:IdP';
$authProcState = [
'saml:sp:IdP' => $idp,
'saml:sp:State' => $state,
'ReturnCall' => ['\SimpleSAML\Module\saml\Auth\Source\SP', 'onProcessingCompleted'],
'Attributes' => $attributes,
'Destination' => $spMetadataArray,
'Source' => $idpMetadataArray,
];
if (isset($state['saml:sp:NameID'])) {
$authProcState['saml:sp:NameID'] = $state['saml:sp:NameID'];
}
if (isset($state['saml:sp:SessionIndex'])) {
$authProcState['saml:sp:SessionIndex'] = $state['saml:sp:SessionIndex'];
}
$pc = new \SimpleSAML\Auth\ProcessingChain($idpMetadataArray, $spMetadataArray, 'sp');
$pc->processState($authProcState);
self::onProcessingCompleted($authProcState);
}
/**
* Handle a logout request from an IdP.
*
* @param string $idpEntityId The entity ID of the IdP.
*/
public function handleLogout($idpEntityId)
{
assert(is_string($idpEntityId));
/* Call the logout callback we registered in onProcessingCompleted(). */
$this->callLogoutCallback($idpEntityId);
}
/**
* Handle an unsolicited login operations.
*
* This method creates a session from the information received. It will
* then redirect to the given URL. This is used to handle IdP initiated
* SSO. This method will never return.
*
* @param string $authId The id of the authentication source that received the request.
* @param array $state A state array.
* @param string $redirectTo The URL we should redirect the user to after updating
* the session. The function will check if the URL is allowed, so there is no need to
* manually check the URL on beforehand. Please refer to the 'trusted.url.domains'
* configuration directive for more information about allowing (or disallowing) URLs.
*/
public static function handleUnsolicitedAuth($authId, array $state, $redirectTo)
{
assert(is_string($authId));
assert(is_string($redirectTo));
$session = \SimpleSAML\Session::getSessionFromRequest();
$session->doLogin($authId, State::getPersistentAuthData($state));
\SimpleSAML\Utils\HTTP::redirectUntrustedURL($redirectTo);
}
/**
* Called when we have completed the procssing chain.
*
* @param array $authProcState The processing chain state.
*/
public static function onProcessingCompleted(array $authProcState)
{
assert(array_key_exists('saml:sp:IdP', $authProcState));
assert(array_key_exists('saml:sp:State', $authProcState));
assert(array_key_exists('Attributes', $authProcState));
$idp = $authProcState['saml:sp:IdP'];
$state = $authProcState['saml:sp:State'];
$sourceId = $state['saml:sp:AuthId'];
$source = Source::getById($sourceId);
if ($source === null) {
throw new \Exception('Could not find authentication source with id '.$sourceId);
}
// Register a callback that we can call if we receive a logout request from the IdP
$source->addLogoutCallback($idp, $state);
$state['Attributes'] = $authProcState['Attributes'];
if (isset($state['saml:sp:isUnsolicited']) && (bool) $state['saml:sp:isUnsolicited']) {
if (!empty($state['saml:sp:RelayState'])) {
$redirectTo = $state['saml:sp:RelayState'];
} else {
$redirectTo = $source->getMetadata()->getString('RelayState', '/');
}
self::handleUnsolicitedAuth($sourceId, $state, $redirectTo);
}
Source::completeAuth($state);
}
}