-
Jaime Perez Crespo authored
Migrate the logout handlers to namespaces, change the base abstract class to be an interface instead.
Jaime Perez Crespo authoredMigrate the logout handlers to namespaces, change the base abstract class to be an interface instead.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
IdP.php 16.10 KiB
<?php
/**
* IdP class.
*
* This class implements the various functions used by IdP.
*
* @package SimpleSAMLphp
*/
class SimpleSAML_IdP
{
/**
* A cache for resolving IdP id's.
*
* @var array
*/
private static $idpCache = array();
/**
* The identifier for this IdP.
*
* @var string
*/
private $id;
/**
* The "association group" for this IdP.
*
* We use this to support cross-protocol logout until
* we implement a cross-protocol IdP.
*
* @var string
*/
private $associationGroup;
/**
* The configuration for this IdP.
*
* @var SimpleSAML_Configuration
*/
private $config;
/**
* Our authsource.
*
* @var SimpleSAML_Auth_Simple
*/
private $authSource;
/**
* Initialize an IdP.
*
* @param string $id The identifier of this IdP.
*
* @throws SimpleSAML_Error_Exception If the IdP is disabled or no such auth source was found.
*/
private function __construct($id)
{
assert('is_string($id)');
$this->id = $id;
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$globalConfig = SimpleSAML_Configuration::getInstance();
if (substr($id, 0, 6) === 'saml2:') {
if (!$globalConfig->getBoolean('enable.saml20-idp', false)) {
throw new SimpleSAML_Error_Exception('enable.saml20-idp disabled in config.php.');
}
$this->config = $metadata->getMetaDataConfig(substr($id, 6), 'saml20-idp-hosted');
} elseif (substr($id, 0, 6) === 'saml1:') {
if (!$globalConfig->getBoolean('enable.shib13-idp', false)) {
throw new SimpleSAML_Error_Exception('enable.shib13-idp disabled in config.php.');
}
$this->config = $metadata->getMetaDataConfig(substr($id, 6), 'shib13-idp-hosted');
} elseif (substr($id, 0, 5) === 'adfs:') {
if (!$globalConfig->getBoolean('enable.adfs-idp', false)) {
throw new SimpleSAML_Error_Exception('enable.adfs-idp disabled in config.php.');
}
$this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted');
try {
// this makes the ADFS IdP use the same SP associations as the SAML 2.0 IdP
$saml2EntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
$this->associationGroup = 'saml2:'.$saml2EntityId;
} catch (Exception $e) {
// probably no SAML 2 IdP configured for this host. Ignore the error
}
} else {
assert(false);
}
if ($this->associationGroup === null) {
$this->associationGroup = $this->id;
}
$auth = $this->config->getString('auth');
if (SimpleSAML_Auth_Source::getById($auth) !== null) {
$this->authSource = new SimpleSAML_Auth_Simple($auth);
} else {
throw new SimpleSAML_Error_Exception('No such "'.$auth.'" auth source found.');
}
}
/**
* Retrieve the ID of this IdP.
*
* @return string The ID of this IdP.
*/
public function getId()
{
return $this->id;
}
/**
* Retrieve an IdP by ID.
*
* @param string $id The identifier of the IdP.
*
* @return SimpleSAML_IdP The IdP.
*/
public static function getById($id)
{
assert('is_string($id)');
if (isset(self::$idpCache[$id])) {
return self::$idpCache[$id];
}
$idp = new self($id);
self::$idpCache[$id] = $idp;
return $idp;
}
/**
* Retrieve the IdP "owning" the state.
*
* @param array &$state The state array.
*
* @return SimpleSAML_IdP The IdP.
*/
public static function getByState(array &$state)
{
assert('isset($state["core:IdP"])');
return self::getById($state['core:IdP']);
}
/**
* Retrieve the configuration for this IdP.
*
* @return SimpleSAML_Configuration The configuration object.
*/
public function getConfig()
{
return $this->config;
}
/**
* Get SP name.
*
* @param string $assocId The association identifier.
*
* @return array|null The name of the SP, as an associative array of language => text, or null if this isn't an SP.
*/
public function getSPName($assocId)
{
assert('is_string($assocId)');
$prefix = substr($assocId, 0, 4);
$spEntityId = substr($assocId, strlen($prefix) + 1);
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
if ($prefix === 'saml') {
try {
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
} catch (Exception $e) {
try {
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'shib13-sp-remote');
} catch (Exception $e) {
return null;
}
}
} else {
if ($prefix === 'adfs') {
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'adfs-sp-remote');
} else {
return null;
}
}
if ($spMetadata->hasValue('name')) {
return $spMetadata->getLocalizedString('name');
} elseif ($spMetadata->hasValue('OrganizationDisplayName')) {
return $spMetadata->getLocalizedString('OrganizationDisplayName');
} else {
return array('en' => $spEntityId);
}
}
/**
* Add an SP association.
*
* @param array $association The SP association.
*/
public function addAssociation(array $association)
{
assert('isset($association["id"])');
assert('isset($association["Handler"])');
$association['core:IdP'] = $this->id;
$session = SimpleSAML_Session::getSessionFromRequest();
$session->addAssociation($this->associationGroup, $association);
}
/**
* Retrieve list of SP associations.
*
* @return array List of SP associations.
*/
public function getAssociations()
{
$session = SimpleSAML_Session::getSessionFromRequest();
return $session->getAssociations($this->associationGroup);
}
/**
* Remove an SP association.
*
* @param string $assocId The association id.
*/
public function terminateAssociation($assocId)
{
assert('is_string($assocId)');
$session = SimpleSAML_Session::getSessionFromRequest();
$session->terminateAssociation($this->associationGroup, $assocId);
}
/**
* Is the current user authenticated?
*
* @return boolean True if the user is authenticated, false otherwise.
*/
public function isAuthenticated()
{
return $this->authSource->isAuthenticated();
}
/**
* Called after authproc has run.
*
* @param array $state The authentication request state array.
*/
public static function postAuthProc(array $state)
{
assert('is_callable($state["Responder"])');
if (isset($state['core:SP'])) {
$session = SimpleSAML_Session::getSessionFromRequest();
$session->setData(
'core:idp-ssotime',
$state['core:IdP'].';'.$state['core:SP'],
time(),
SimpleSAML_Session::DATA_TIMEOUT_SESSION_END
);
}
call_user_func($state['Responder'], $state);
assert('FALSE');
}
/**
* The user is authenticated.
*
* @param array $state The authentication request state array.
*
* @throws SimpleSAML_Error_Exception If we are not authenticated.
*/
public static function postAuth(array $state)
{
$idp = SimpleSAML_IdP::getByState($state);
if (!$idp->isAuthenticated()) {
throw new SimpleSAML_Error_Exception('Not authenticated.');
}
$state['Attributes'] = $idp->authSource->getAttributes();
if (isset($state['SPMetadata'])) {
$spMetadata = $state['SPMetadata'];
} else {
$spMetadata = array();
}
if (isset($state['core:SP'])) {
$session = SimpleSAML_Session::getSessionFromRequest();
$previousSSOTime = $session->getData('core:idp-ssotime', $state['core:IdP'].';'.$state['core:SP']);
if ($previousSSOTime !== null) {
$state['PreviousSSOTimestamp'] = $previousSSOTime;
}
}
$idpMetadata = $idp->getConfig()->toArray();
$pc = new SimpleSAML_Auth_ProcessingChain($idpMetadata, $spMetadata, 'idp');
$state['ReturnCall'] = array('SimpleSAML_IdP', 'postAuthProc');
$state['Destination'] = $spMetadata;
$state['Source'] = $idpMetadata;
$pc->processState($state);
self::postAuthProc($state);
}
/**
* Authenticate the user.
*
* This function authenticates the user.
*
* @param array &$state The authentication request state.
*
* @throws SimpleSAML_Error_NoPassive If we were asked to do passive authentication.
*/
private function authenticate(array &$state)
{
if (isset($state['isPassive']) && (bool) $state['isPassive']) {
throw new SimpleSAML_Error_NoPassive('Passive authentication not supported.');
}
$this->authSource->login($state);
}
/**
* Re-authenticate the user.
*
* This function re-authenticates an user with an existing session. This gives the authentication source a chance
* to do additional work when re-authenticating for SSO.
*
* Note: This function is not used when ForceAuthn=true.
*
* @param array &$state The authentication request state.
*
* @throws SimpleSAML_Error_Exception If there is no auth source defined for this IdP.
*/
private function reauthenticate(array &$state)
{
$sourceImpl = $this->authSource->getAuthSource();
if ($sourceImpl === null) {
throw new SimpleSAML_Error_Exception('No such auth source defined.');
}
$sourceImpl->reauthenticate($state);
}
/**
* Process authentication requests.
*
* @param array &$state The authentication request state.
*/
public function handleAuthenticationRequest(array &$state)
{
assert('isset($state["Responder"])');
$state['core:IdP'] = $this->id;
if (isset($state['SPMetadata']['entityid'])) {
$spEntityId = $state['SPMetadata']['entityid'];
} elseif (isset($state['SPMetadata']['entityID'])) {
$spEntityId = $state['SPMetadata']['entityID'];
} else {
$spEntityId = null;
}
$state['core:SP'] = $spEntityId;
// first, check whether we need to authenticate the user
if (isset($state['ForceAuthn']) && (bool) $state['ForceAuthn']) {
// force authentication is in effect
$needAuth = true;
} else {
$needAuth = !$this->isAuthenticated();
}
$state['IdPMetadata'] = $this->getConfig()->toArray();
$state['ReturnCallback'] = array('SimpleSAML_IdP', 'postAuth');
try {
if ($needAuth) {
$this->authenticate($state);
assert('FALSE');
} else {
$this->reauthenticate($state);
}
$this->postAuth($state);
} catch (SimpleSAML_Error_Exception $e) {
SimpleSAML_Auth_State::throwException($state, $e);
} catch (Exception $e) {
$e = new SimpleSAML_Error_UnserializableException($e);
SimpleSAML_Auth_State::throwException($state, $e);
}
}
/**
* Find the logout handler of this IdP.
*
* @return \SimpleSAML\IdP\LogoutHandlerInterface The logout handler class.
*
* @throws SimpleSAML_Error_Exception If we cannot find a logout handler.
*/
public function getLogoutHandler()
{
// find the logout handler
$logouttype = $this->getConfig()->getString('logouttype', 'traditional');
switch ($logouttype) {
case 'traditional':
$handler = 'SimpleSAML\IdP\TraditionalLogoutHandler';
break;
case 'iframe':
$handler = 'SimpleSAML\IdP\IFrameLogoutHandler';
break;
default:
throw new SimpleSAML_Error_Exception('Unknown logout handler: '.var_export($logouttype, true));
}
return new $handler($this);
}
/**
* Finish the logout operation.
*
* This function will never return.
*
* @param array &$state The logout request state.
*/
public function finishLogout(array &$state)
{
assert('isset($state["Responder"])');
$idp = SimpleSAML_IdP::getByState($state);
call_user_func($state['Responder'], $idp, $state);
assert('false');
}
/**
* Process a logout request.
*
* This function will never return.
*
* @param array &$state The logout request state.
* @param string|null $assocId The association we received the logout request from, or null if there was no
* association.
*/
public function handleLogoutRequest(array &$state, $assocId)
{
assert('isset($state["Responder"])');
assert('is_string($assocId) || is_null($assocId)');
$state['core:IdP'] = $this->id;
$state['core:TerminatedAssocId'] = $assocId;
if ($assocId !== null) {
$this->terminateAssociation($assocId);
$session = SimpleSAML_Session::getSessionFromRequest();
$session->deleteData('core:idp-ssotime', $this->id.':'.$state['saml:SPEntityId']);
}
// terminate the local session
$id = SimpleSAML_Auth_State::saveState($state, 'core:Logout:afterbridge');
$returnTo = SimpleSAML\Module::getModuleURL('core/idp/resumelogout.php', array('id' => $id));
$this->authSource->logout($returnTo);
$handler = $this->getLogoutHandler();
$handler->startLogout($state, $assocId);
assert('false');
}
/**
* Process a logout response.
*
* This function will never return.
*
* @param string $assocId The association that is terminated.
* @param string|null $relayState The RelayState from the start of the logout.
* @param SimpleSAML_Error_Exception|null $error The error that occurred during session termination (if any).
*/
public function handleLogoutResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = null)
{
assert('is_string($assocId)');
assert('is_string($relayState) || is_null($relayState)');
$session = SimpleSAML_Session::getSessionFromRequest();
$session->deleteData('core:idp-ssotime', $this->id.';'.substr($assocId, strpos($assocId, ':') + 1));
$handler = $this->getLogoutHandler();
$handler->onResponse($assocId, $relayState, $error);
assert('false');
}
/**
* Log out, then redirect to a URL.
*
* This function never returns.
*
* @param string $url The URL the user should be returned to after logout.
*/
public function doLogoutRedirect($url)
{
assert('is_string($url)');
$state = array(
'Responder' => array('SimpleSAML_IdP', 'finishLogoutRedirect'),
'core:Logout:URL' => $url,
);
$this->handleLogoutRequest($state, null);
assert('false');
}
/**
* Redirect to a URL after logout.
*
* This function never returns.
*
* @param SimpleSAML_IdP $idp Deprecated. Will be removed.
* @param array &$state The logout state from doLogoutRedirect().
*/
public static function finishLogoutRedirect(SimpleSAML_IdP $idp, array $state)
{
assert('isset($state["core:Logout:URL"])');
\SimpleSAML\Utils\HTTP::redirectTrustedURL($state['core:Logout:URL']);
assert('false');
}
}