runff 1.0 commit
This commit is contained in:
558
lib/SimpleSAML/IdP.php
Executable file
558
lib/SimpleSAML/IdP.php
Executable file
@@ -0,0 +1,558 @@
|
||||
<?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\Module\saml\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\Module\saml\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) || $assocId === null);
|
||||
|
||||
$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) || $relayState === null);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user