Skip to content
Snippets Groups Projects
Commit 70bdb789 authored by Jaime Pérez's avatar Jaime Pérez
Browse files

Reformat sspmod_saml_IdP_SAML2 to comply with PSR-2 standards.

parent 0f89c1f7
No related branches found
No related tags found
No related merge requests found
<?php
use RobRichards\XMLSecLibs\XMLSecurityKey;
/**
* IdP implementation for SAML 2.0 protocol.
*
* @package SimpleSAMLphp
*/
class sspmod_saml_IdP_SAML2 {
/**
* Send a response to the SP.
*
* @param array $state The authentication state.
*/
public static function sendResponse(array $state) {
assert('isset($state["Attributes"])');
assert('isset($state["SPMetadata"])');
assert('isset($state["saml:ConsumerURL"])');
assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spMetadata = $state["SPMetadata"];
$spEntityId = $spMetadata['entityid'];
$spMetadata = SimpleSAML_Configuration::loadFromArray($spMetadata,
'$metadata[' . var_export($spEntityId, TRUE) . ']');
SimpleSAML\Logger::info('Sending SAML 2.0 Response to ' . var_export($spEntityId, TRUE));
$requestId = $state['saml:RequestId'];
$relayState = $state['saml:RelayState'];
$consumerURL = $state['saml:ConsumerURL'];
$protocolBinding = $state['saml:Binding'];
$idp = SimpleSAML_IdP::getByState($state);
$idpMetadata = $idp->getConfig();
$assertion = self::buildAssertion($idpMetadata, $spMetadata, $state);
if (isset($state['saml:AuthenticatingAuthority'])) {
$assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']);
}
// Create the session association (for logout).
$association = array(
'id' => 'saml:' . $spEntityId,
'Handler' => 'sspmod_saml_IdP_SAML2',
'Expires' => $assertion->getSessionNotOnOrAfter(),
'saml:entityID' => $spEntityId,
'saml:NameID' => $state['saml:idp:NameID'],
'saml:SessionIndex' => $assertion->getSessionIndex(),
);
// Maybe encrypt the assertion.
$assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion);
/* Create the response. */
$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
$ar->setInResponseTo($requestId);
$ar->setRelayState($relayState);
$ar->setAssertions(array($assertion));
/* Register the session association with the IdP. */
$idp->addAssociation($association);
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'protocol' => 'saml2',
);
if (isset($state['saml:AuthnRequestReceivedAt'])) {
$statsData['logintime'] = microtime(TRUE) - $state['saml:AuthnRequestReceivedAt'];
}
SimpleSAML_Stats::log('saml:idp:Response', $statsData);
/* Send the response. */
$binding = \SAML2\Binding::getBinding($protocolBinding);
$binding->send($ar);
}
/**
* Handle authentication error.
*
* SimpleSAML_Error_Exception $exception The exception.
* @param array $state The error state.
*/
public static function handleAuthError(SimpleSAML_Error_Exception $exception, array $state) {
assert('isset($state["SPMetadata"])');
assert('isset($state["saml:ConsumerURL"])');
assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL.
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spMetadata = $state["SPMetadata"];
$spEntityId = $spMetadata['entityid'];
$spMetadata = SimpleSAML_Configuration::loadFromArray($spMetadata,
'$metadata[' . var_export($spEntityId, TRUE) . ']');
$requestId = $state['saml:RequestId'];
$relayState = $state['saml:RelayState'];
$consumerURL = $state['saml:ConsumerURL'];
$protocolBinding = $state['saml:Binding'];
$idp = SimpleSAML_IdP::getByState($state);
$idpMetadata = $idp->getConfig();
$error = sspmod_saml_Error::fromException($exception);
SimpleSAML\Logger::warning("Returning error to SP with entity ID '".var_export($spEntityId, TRUE)."'.");
$exception->log(SimpleSAML\Logger::WARNING);
$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
$ar->setInResponseTo($requestId);
$ar->setRelayState($relayState);
$status = array(
'Code' => $error->getStatus(),
'SubCode' => $error->getSubStatus(),
'Message' => $error->getStatusMessage(),
);
$ar->setStatus($status);
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'protocol' => 'saml2',
'error' => $status,
);
if (isset($state['saml:AuthnRequestReceivedAt'])) {
$statsData['logintime'] = microtime(TRUE) - $state['saml:AuthnRequestReceivedAt'];
}
SimpleSAML_Stats::log('saml:idp:Response:error', $statsData);
$binding = \SAML2\Binding::getBinding($protocolBinding);
$binding->send($ar);
}
/**
* Find SP AssertionConsumerService based on parameter in AuthnRequest.
*
* @param array $supportedBindings The bindings we allow for the response.
* @param SimpleSAML_Configuration $spMetadata The metadata for the SP.
* @param string|NULL $AssertionConsumerServiceURL AssertionConsumerServiceURL from request.
* @param string|NULL $ProtocolBinding ProtocolBinding from request.
* @param int|NULL $AssertionConsumerServiceIndex AssertionConsumerServiceIndex from request.
* @return array Array with the Location and Binding we should use for the response.
*/
private static function getAssertionConsumerService(array $supportedBindings, SimpleSAML_Configuration $spMetadata,
$AssertionConsumerServiceURL, $ProtocolBinding, $AssertionConsumerServiceIndex) {
assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)');
assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)');
assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)');
/* We want to pick the best matching endpoint in the case where for example
* only the ProtocolBinding is given. We therefore pick endpoints with the
* following priority:
* 1. isDefault="true"
* 2. isDefault unset
* 3. isDefault="false"
*/
$firstNotFalse = NULL;
$firstFalse = NULL;
foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
if ($AssertionConsumerServiceURL !== NULL && $ep['Location'] !== $AssertionConsumerServiceURL) {
continue;
}
if ($ProtocolBinding !== NULL && $ep['Binding'] !== $ProtocolBinding) {
continue;
}
if ($AssertionConsumerServiceIndex !== NULL && $ep['index'] !== $AssertionConsumerServiceIndex) {
continue;
}
if (!in_array($ep['Binding'], $supportedBindings, TRUE)) {
/* The endpoint has an unsupported binding. */
continue;
}
/* We have an endpoint that matches all our requirements. Check if it is the best one. */
if (array_key_exists('isDefault', $ep)) {
if ($ep['isDefault'] === TRUE) {
/* This is the first matching endpoint with isDefault set to TRUE. */
return $ep;
}
/* isDefault is set to FALSE, but the endpoint is still useable. */
if ($firstFalse === NULL) {
/* This is the first endpoint that we can use. */
$firstFalse = $ep;
}
} else if ($firstNotFalse === NULL) {
/* This is the first endpoint without isDefault set. */
$firstNotFalse = $ep;
}
}
if ($firstNotFalse !== NULL) {
return $firstNotFalse;
} elseif ($firstFalse !== NULL) {
return $firstFalse;
}
SimpleSAML\Logger::warning('Authentication request specifies invalid AssertionConsumerService:');
if ($AssertionConsumerServiceURL !== NULL) {
SimpleSAML\Logger::warning('AssertionConsumerServiceURL: ' . var_export($AssertionConsumerServiceURL, TRUE));
}
if ($ProtocolBinding !== NULL) {
SimpleSAML\Logger::warning('ProtocolBinding: ' . var_export($ProtocolBinding, TRUE));
}
if ($AssertionConsumerServiceIndex !== NULL) {
SimpleSAML\Logger::warning('AssertionConsumerServiceIndex: ' . var_export($AssertionConsumerServiceIndex, TRUE));
}
/* We have no good endpoints. Our last resort is to just use the default endpoint. */
return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings);
}
/**
* Receive an authentication request.
*
* @param SimpleSAML_IdP $idp The IdP we are receiving it for.
*/
public static function receiveAuthnRequest(SimpleSAML_IdP $idp) {
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$supportedBindings = array(\SAML2\Constants::BINDING_HTTP_POST);
if ($idpMetadata->getBoolean('saml20.sendartifact', FALSE)) {
$supportedBindings[] = \SAML2\Constants::BINDING_HTTP_ARTIFACT;
}
if ($idpMetadata->getBoolean('saml20.hok.assertion', FALSE)) {
$supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
}
if (isset($_REQUEST['spentityid'])) {
/* IdP initiated authentication. */
if (isset($_REQUEST['cookieTime'])) {
$cookieTime = (int)$_REQUEST['cookieTime'];
if ($cookieTime + 5 > time()) {
/*
* Less than five seconds has passed since we were
* here the last time. Cookies are probably disabled.
*/
\SimpleSAML\Utils\HTTP::checkSessionCookie(\SimpleSAML\Utils\HTTP::getSelfURL());
}
}
$spEntityId = (string)$_REQUEST['spentityid'];
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
if (isset($_REQUEST['RelayState'])) {
$relayState = (string)$_REQUEST['RelayState'];
} else {
$relayState = NULL;
}
if (isset($_REQUEST['binding'])){
$protocolBinding = (string)$_REQUEST['binding'];
} else {
$protocolBinding = NULL;
}
if (isset($_REQUEST['NameIDFormat'])) {
$nameIDFormat = (string)$_REQUEST['NameIDFormat'];
} else {
$nameIDFormat = NULL;
}
$requestId = NULL;
$IDPList = array();
$ProxyCount = NULL;
$RequesterID = NULL;
$forceAuthn = FALSE;
$isPassive = FALSE;
$consumerURL = NULL;
$consumerIndex = NULL;
$extensions = NULL;
$allowCreate = TRUE;
$authnContext = null;
$idpInit = TRUE;
SimpleSAML\Logger::info('SAML2.0 - IdP.SSOService: IdP initiated authentication: '. var_export($spEntityId, TRUE));
} else {
$binding = \SAML2\Binding::getCurrentBinding();
$request = $binding->receive();
if (!($request instanceof \SAML2\AuthnRequest)) {
throw new SimpleSAML_Error_BadRequest('Message received on authentication request endpoint wasn\'t an authentication request.');
}
$spEntityId = $request->getIssuer();
if ($spEntityId === NULL) {
throw new SimpleSAML_Error_BadRequest('Received message on authentication request endpoint without issuer.');
}
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $request);
$relayState = $request->getRelayState();
$requestId = $request->getId();
$IDPList = $request->getIDPList();
$ProxyCount = $request->getProxyCount();
if ($ProxyCount !== null) $ProxyCount--;
$RequesterID = $request->getRequesterID();
$forceAuthn = $request->getForceAuthn();
$isPassive = $request->getIsPassive();
$consumerURL = $request->getAssertionConsumerServiceURL();
$protocolBinding = $request->getProtocolBinding();
$consumerIndex = $request->getAssertionConsumerServiceIndex();
$extensions = $request->getExtensions();
$authnContext = $request->getRequestedAuthnContext();
$nameIdPolicy = $request->getNameIdPolicy();
if (isset($nameIdPolicy['Format'])) {
$nameIDFormat = $nameIdPolicy['Format'];
} else {
$nameIDFormat = NULL;
}
if (isset($nameIdPolicy['AllowCreate'])) {
$allowCreate = $nameIdPolicy['AllowCreate'];
} else {
$allowCreate = FALSE;
}
$idpInit = FALSE;
SimpleSAML\Logger::info('SAML2.0 - IdP.SSOService: incoming authentication request: '. var_export($spEntityId, TRUE));
}
SimpleSAML_Stats::log('saml:idp:AuthnRequest', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'forceAuthn' => $forceAuthn,
'isPassive' => $isPassive,
'protocol' => 'saml2',
'idpInit' => $idpInit,
));
$acsEndpoint = self::getAssertionConsumerService($supportedBindings, $spMetadata, $consumerURL, $protocolBinding, $consumerIndex);
$IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array())));
if ($ProxyCount === null) $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
if (!$forceAuthn) {
$forceAuthn = $spMetadata->getBoolean('ForceAuthn', FALSE);
}
$sessionLostParams = array(
'spentityid' => $spEntityId,
'cookieTime' => time(),
);
if ($relayState !== NULL) {
$sessionLostParams['RelayState'] = $relayState;
}
$sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
class sspmod_saml_IdP_SAML2
{
/**
* Send a response to the SP.
*
* @param array $state The authentication state.
*/
public static function sendResponse(array $state)
{
assert('isset($state["Attributes"])');
assert('isset($state["SPMetadata"])');
assert('isset($state["saml:ConsumerURL"])');
assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spMetadata = $state["SPMetadata"];
$spEntityId = $spMetadata['entityid'];
$spMetadata = SimpleSAML_Configuration::loadFromArray(
$spMetadata,
'$metadata['.var_export($spEntityId, true).']'
);
SimpleSAML\Logger::info('Sending SAML 2.0 Response to '.var_export($spEntityId, true));
$requestId = $state['saml:RequestId'];
$relayState = $state['saml:RelayState'];
$consumerURL = $state['saml:ConsumerURL'];
$protocolBinding = $state['saml:Binding'];
$idp = SimpleSAML_IdP::getByState($state);
$idpMetadata = $idp->getConfig();
$assertion = self::buildAssertion($idpMetadata, $spMetadata, $state);
if (isset($state['saml:AuthenticatingAuthority'])) {
$assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']);
}
// create the session association (for logout)
$association = array(
'id' => 'saml:'.$spEntityId,
'Handler' => 'sspmod_saml_IdP_SAML2',
'Expires' => $assertion->getSessionNotOnOrAfter(),
'saml:entityID' => $spEntityId,
'saml:NameID' => $state['saml:idp:NameID'],
'saml:SessionIndex' => $assertion->getSessionIndex(),
);
// maybe encrypt the assertion
$assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion);
// create the response
$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
$ar->setInResponseTo($requestId);
$ar->setRelayState($relayState);
$ar->setAssertions(array($assertion));
// register the session association with the IdP
$idp->addAssociation($association);
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'protocol' => 'saml2',
);
if (isset($state['saml:AuthnRequestReceivedAt'])) {
$statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
}
SimpleSAML_Stats::log('saml:idp:Response', $statsData);
// send the response
$binding = \SAML2\Binding::getBinding($protocolBinding);
$binding->send($ar);
}
/**
* Handle authentication error.
*
* SimpleSAML_Error_Exception $exception The exception.
*
* @param array $state The error state.
*/
public static function handleAuthError(SimpleSAML_Error_Exception $exception, array $state)
{
assert('isset($state["SPMetadata"])');
assert('isset($state["saml:ConsumerURL"])');
assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL.
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spMetadata = $state["SPMetadata"];
$spEntityId = $spMetadata['entityid'];
$spMetadata = SimpleSAML_Configuration::loadFromArray(
$spMetadata,
'$metadata['.var_export($spEntityId, true).']'
);
$requestId = $state['saml:RequestId'];
$relayState = $state['saml:RelayState'];
$consumerURL = $state['saml:ConsumerURL'];
$protocolBinding = $state['saml:Binding'];
$idp = SimpleSAML_IdP::getByState($state);
$idpMetadata = $idp->getConfig();
$error = sspmod_saml_Error::fromException($exception);
SimpleSAML\Logger::warning("Returning error to SP with entity ID '".var_export($spEntityId, true)."'.");
$exception->log(SimpleSAML\Logger::WARNING);
$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
$ar->setInResponseTo($requestId);
$ar->setRelayState($relayState);
$status = array(
'Code' => $error->getStatus(),
'SubCode' => $error->getSubStatus(),
'Message' => $error->getStatusMessage(),
);
$ar->setStatus($status);
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'protocol' => 'saml2',
'error' => $status,
);
if (isset($state['saml:AuthnRequestReceivedAt'])) {
$statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
}
SimpleSAML_Stats::log('saml:idp:Response:error', $statsData);
$binding = \SAML2\Binding::getBinding($protocolBinding);
$binding->send($ar);
}
/**
* Find SP AssertionConsumerService based on parameter in AuthnRequest.
*
* @param array $supportedBindings The bindings we allow for the response.
* @param SimpleSAML_Configuration $spMetadata The metadata for the SP.
* @param string|NULL $AssertionConsumerServiceURL AssertionConsumerServiceURL from request.
* @param string|NULL $ProtocolBinding ProtocolBinding from request.
* @param int|NULL $AssertionConsumerServiceIndex AssertionConsumerServiceIndex from request.
*
* @return array Array with the Location and Binding we should use for the response.
*/
private static function getAssertionConsumerService(
array $supportedBindings,
SimpleSAML_Configuration $spMetadata,
$AssertionConsumerServiceURL,
$ProtocolBinding,
$AssertionConsumerServiceIndex
) {
assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)');
assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)');
assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)');
/* We want to pick the best matching endpoint in the case where for example
* only the ProtocolBinding is given. We therefore pick endpoints with the
* following priority:
* 1. isDefault="true"
* 2. isDefault unset
* 3. isDefault="false"
*/
$firstNotFalse = null;
$firstFalse = null;
foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
if ($AssertionConsumerServiceURL !== null && $ep['Location'] !== $AssertionConsumerServiceURL) {
continue;
}
if ($ProtocolBinding !== null && $ep['Binding'] !== $ProtocolBinding) {
continue;
}
if ($AssertionConsumerServiceIndex !== null && $ep['index'] !== $AssertionConsumerServiceIndex) {
continue;
}
if (!in_array($ep['Binding'], $supportedBindings, true)) {
/* The endpoint has an unsupported binding. */
continue;
}
// we have an endpoint that matches all our requirements. Check if it is the best one
if (array_key_exists('isDefault', $ep)) {
if ($ep['isDefault'] === true) {
// this is the first matching endpoint with isDefault set to true
return $ep;
}
// isDefault is set to FALSE, but the endpoint is still usable
if ($firstFalse === null) {
// this is the first endpoint that we can use
$firstFalse = $ep;
}
} else {
if ($firstNotFalse === null) {
// this is the first endpoint without isDefault set
$firstNotFalse = $ep;
}
}
}
if ($firstNotFalse !== null) {
return $firstNotFalse;
} elseif ($firstFalse !== null) {
return $firstFalse;
}
SimpleSAML\Logger::warning('Authentication request specifies invalid AssertionConsumerService:');
if ($AssertionConsumerServiceURL !== null) {
SimpleSAML\Logger::warning('AssertionConsumerServiceURL: '.var_export($AssertionConsumerServiceURL, true));
}
if ($ProtocolBinding !== null) {
SimpleSAML\Logger::warning('ProtocolBinding: '.var_export($ProtocolBinding, true));
}
if ($AssertionConsumerServiceIndex !== null) {
SimpleSAML\Logger::warning(
'AssertionConsumerServiceIndex: '.var_export($AssertionConsumerServiceIndex, true)
);
}
// we have no good endpoints. Our last resort is to just use the default endpoint
return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings);
}
/**
* Receive an authentication request.
*
* @param SimpleSAML_IdP $idp The IdP we are receiving it for.
* @throws SimpleSAML_Error_BadRequest In case an error occurs when trying to receive the request.
*/
public static function receiveAuthnRequest(SimpleSAML_IdP $idp)
{
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$supportedBindings = array(\SAML2\Constants::BINDING_HTTP_POST);
if ($idpMetadata->getBoolean('saml20.sendartifact', false)) {
$supportedBindings[] = \SAML2\Constants::BINDING_HTTP_ARTIFACT;
}
if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) {
$supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
}
if (isset($_REQUEST['spentityid'])) {
/* IdP initiated authentication. */
if (isset($_REQUEST['cookieTime'])) {
$cookieTime = (int) $_REQUEST['cookieTime'];
if ($cookieTime + 5 > time()) {
/*
* Less than five seconds has passed since we were
* here the last time. Cookies are probably disabled.
*/
\SimpleSAML\Utils\HTTP::checkSessionCookie(\SimpleSAML\Utils\HTTP::getSelfURL());
}
}
$spEntityId = (string) $_REQUEST['spentityid'];
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
if (isset($_REQUEST['RelayState'])) {
$relayState = (string) $_REQUEST['RelayState'];
} else {
$relayState = null;
}
if (isset($_REQUEST['binding'])) {
$protocolBinding = (string) $_REQUEST['binding'];
} else {
$protocolBinding = null;
}
if (isset($_REQUEST['NameIDFormat'])) {
$nameIDFormat = (string) $_REQUEST['NameIDFormat'];
} else {
$nameIDFormat = null;
}
$requestId = null;
$IDPList = array();
$ProxyCount = null;
$RequesterID = null;
$forceAuthn = false;
$isPassive = false;
$consumerURL = null;
$consumerIndex = null;
$extensions = null;
$allowCreate = true;
$authnContext = null;
$idpInit = true;
SimpleSAML\Logger::info(
'SAML2.0 - IdP.SSOService: IdP initiated authentication: '.var_export($spEntityId, true)
);
} else {
$binding = \SAML2\Binding::getCurrentBinding();
$request = $binding->receive();
if (!($request instanceof \SAML2\AuthnRequest)) {
throw new SimpleSAML_Error_BadRequest(
'Message received on authentication request endpoint wasn\'t an authentication request.'
);
}
$spEntityId = $request->getIssuer();
if ($spEntityId === null) {
throw new SimpleSAML_Error_BadRequest(
'Received message on authentication request endpoint without issuer.'
);
}
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $request);
$relayState = $request->getRelayState();
$requestId = $request->getId();
$IDPList = $request->getIDPList();
$ProxyCount = $request->getProxyCount();
if ($ProxyCount !== null) {
$ProxyCount--;
}
$RequesterID = $request->getRequesterID();
$forceAuthn = $request->getForceAuthn();
$isPassive = $request->getIsPassive();
$consumerURL = $request->getAssertionConsumerServiceURL();
$protocolBinding = $request->getProtocolBinding();
$consumerIndex = $request->getAssertionConsumerServiceIndex();
$extensions = $request->getExtensions();
$authnContext = $request->getRequestedAuthnContext();
$nameIdPolicy = $request->getNameIdPolicy();
if (isset($nameIdPolicy['Format'])) {
$nameIDFormat = $nameIdPolicy['Format'];
} else {
$nameIDFormat = null;
}
if (isset($nameIdPolicy['AllowCreate'])) {
$allowCreate = $nameIdPolicy['AllowCreate'];
} else {
$allowCreate = false;
}
$idpInit = false;
SimpleSAML\Logger::info(
'SAML2.0 - IdP.SSOService: incoming authentication request: '.var_export($spEntityId, true)
);
}
SimpleSAML_Stats::log('saml:idp:AuthnRequest', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'forceAuthn' => $forceAuthn,
'isPassive' => $isPassive,
'protocol' => 'saml2',
'idpInit' => $idpInit,
));
$acsEndpoint = self::getAssertionConsumerService(
$supportedBindings,
$spMetadata,
$consumerURL,
$protocolBinding,
$consumerIndex
);
$IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array())));
if ($ProxyCount === null) {
$ProxyCount = $spMetadata->getInteger('ProxyCount', null);
}
if (!$forceAuthn) {
$forceAuthn = $spMetadata->getBoolean('ForceAuthn', false);
}
$sessionLostParams = array(
'spentityid' => $spEntityId,
'cookieTime' => time(),
);
if ($relayState !== null) {
$sessionLostParams['RelayState'] = $relayState;
}
$sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
\SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
$sessionLostParams);
$state = array(
'Responder' => array('sspmod_saml_IdP_SAML2', 'sendResponse'),
SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC => array('sspmod_saml_IdP_SAML2', 'handleAuthError'),
SimpleSAML_Auth_State::RESTART => $sessionLostURL,
'SPMetadata' => $spMetadata->toArray(),
'saml:RelayState' => $relayState,
'saml:RequestId' => $requestId,
'saml:IDPList' => $IDPList,
'saml:ProxyCount' => $ProxyCount,
'saml:RequesterID' => $RequesterID,
'ForceAuthn' => $forceAuthn,
'isPassive' => $isPassive,
'saml:ConsumerURL' => $acsEndpoint['Location'],
'saml:Binding' => $acsEndpoint['Binding'],
'saml:NameIDFormat' => $nameIDFormat,
'saml:AllowCreate' => $allowCreate,
'saml:Extensions' => $extensions,
'saml:AuthnRequestReceivedAt' => microtime(TRUE),
'saml:RequestedAuthnContext' => $authnContext,
);
$idp->handleAuthenticationRequest($state);
}
/**
* Send a logout request to a given association.
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array $association The association that should be terminated.
* @param string|NULL $relayState An id that should be carried across the logout.
*/
public static function sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState) {
assert('is_string($relayState) || is_null($relayState)');
SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '. var_export($association['saml:entityID'], TRUE));
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
'spEntityID' => $association['saml:entityID'],
'idpEntityID' => $idpMetadata->getString('entityid'),
));
$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST)
);
$binding = \SAML2\Binding::getBinding($dst['Binding']);
$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
$lr->setDestination($dst['Location']);
$binding->send($lr);
}
/**
* Send a logout response.
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array &$state The logout state array.
*/
public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state) {
assert('isset($state["saml:SPEntityId"])');
assert('isset($state["saml:RequestId"])');
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spEntityId = $state['saml:SPEntityId'];
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
$lr = sspmod_saml_Message::buildLogoutResponse($idpMetadata, $spMetadata);
$lr->setInResponseTo($state['saml:RequestId']);
$lr->setRelayState($state['saml:RelayState']);
if (isset($state['core:Failed']) && $state['core:Failed']) {
$partial = TRUE;
$lr->setStatus(array(
'Code' => \SAML2\Constants::STATUS_SUCCESS,
'SubCode' => \SAML2\Constants::STATUS_PARTIAL_LOGOUT,
));
SimpleSAML\Logger::info('Sending logout response for partial logout to SP ' . var_export($spEntityId, TRUE));
} else {
$partial = FALSE;
SimpleSAML\Logger::debug('Sending logout response to SP ' . var_export($spEntityId, TRUE));
}
SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'partial' => $partial
));
$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST)
);
$binding = \SAML2\Binding::getBinding($dst['Binding']);
if (isset($dst['ResponseLocation'])) {
$dst = $dst['ResponseLocation'];
} else {
$dst = $dst['Location'];
}
$lr->setDestination($dst);
$binding->send($lr);
}
/**
* Receive a logout message.
*
* @param SimpleSAML_IdP $idp The IdP we are receiving it for.
*/
public static function receiveLogoutMessage(SimpleSAML_IdP $idp) {
$binding = \SAML2\Binding::getCurrentBinding();
$message = $binding->receive();
$spEntityId = $message->getIssuer();
if ($spEntityId === NULL) {
/* Without an issuer we have no way to respond to the message. */
throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
}
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $message);
if ($message instanceof \SAML2\LogoutResponse) {
SimpleSAML\Logger::info('Received SAML 2.0 LogoutResponse from: '. var_export($spEntityId, TRUE));
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
);
if (!$message->isSuccess()) {
$statsData['error'] = $message->getStatus();
}
SimpleSAML_Stats::log('saml:idp:LogoutResponse:recv', $statsData);
$relayState = $message->getRelayState();
if (!$message->isSuccess()) {
$logoutError = sspmod_saml_Message::getResponseError($message);
SimpleSAML\Logger::warning('Unsuccessful logout. Status was: ' . $logoutError);
} else {
$logoutError = NULL;
}
$assocId = 'saml:' . $spEntityId;
$idp->handleLogoutResponse($assocId, $relayState, $logoutError);
} elseif ($message instanceof \SAML2\LogoutRequest) {
SimpleSAML\Logger::info('Received SAML 2.0 LogoutRequest from: '. var_export($spEntityId, TRUE));
SimpleSAML_Stats::log('saml:idp:LogoutRequest:recv', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
));
$spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
SimpleSAML\Logger::stats('saml20-idp-SLO spinit ' . $spStatsId . ' ' . $idpMetadata->getString('entityid'));
$state = array(
'Responder' => array('sspmod_saml_IdP_SAML2', 'sendLogoutResponse'),
'saml:SPEntityId' => $spEntityId,
'saml:RelayState' => $message->getRelayState(),
'saml:RequestId' => $message->getId(),
);
$assocId = 'saml:' . $spEntityId;
$idp->handleLogoutRequest($state, $assocId);
} else {
throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message));
}
}
/**
$sessionLostParams
);
$state = array(
'Responder' => array('sspmod_saml_IdP_SAML2', 'sendResponse'),
SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC => array('sspmod_saml_IdP_SAML2', 'handleAuthError'),
SimpleSAML_Auth_State::RESTART => $sessionLostURL,
'SPMetadata' => $spMetadata->toArray(),
'saml:RelayState' => $relayState,
'saml:RequestId' => $requestId,
'saml:IDPList' => $IDPList,
'saml:ProxyCount' => $ProxyCount,
'saml:RequesterID' => $RequesterID,
'ForceAuthn' => $forceAuthn,
'isPassive' => $isPassive,
'saml:ConsumerURL' => $acsEndpoint['Location'],
'saml:Binding' => $acsEndpoint['Binding'],
'saml:NameIDFormat' => $nameIDFormat,
'saml:AllowCreate' => $allowCreate,
'saml:Extensions' => $extensions,
'saml:AuthnRequestReceivedAt' => microtime(true),
'saml:RequestedAuthnContext' => $authnContext,
);
$idp->handleAuthenticationRequest($state);
}
/**
* Send a logout request to a given association.
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array $association The association that should be terminated.
* @param string|NULL $relayState An id that should be carried across the logout.
*/
public static function sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState)
{
assert('is_string($relayState) || is_null($relayState)');
SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
'spEntityID' => $association['saml:entityID'],
'idpEntityID' => $idpMetadata->getString('entityid'),
));
$dst = $spMetadata->getEndpointPrioritizedByBinding(
'SingleLogoutService',
array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST
)
);
$binding = \SAML2\Binding::getBinding($dst['Binding']);
$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
$lr->setDestination($dst['Location']);
$binding->send($lr);
}
/**
* Send a logout response.
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array &$state The logout state array.
*/
public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state)
{
assert('isset($state["saml:SPEntityId"])');
assert('isset($state["saml:RequestId"])');
assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
$spEntityId = $state['saml:SPEntityId'];
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
$lr = sspmod_saml_Message::buildLogoutResponse($idpMetadata, $spMetadata);
$lr->setInResponseTo($state['saml:RequestId']);
$lr->setRelayState($state['saml:RelayState']);
if (isset($state['core:Failed']) && $state['core:Failed']) {
$partial = true;
$lr->setStatus(array(
'Code' => \SAML2\Constants::STATUS_SUCCESS,
'SubCode' => \SAML2\Constants::STATUS_PARTIAL_LOGOUT,
));
SimpleSAML\Logger::info('Sending logout response for partial logout to SP '.var_export($spEntityId, true));
} else {
$partial = false;
SimpleSAML\Logger::debug('Sending logout response to SP '.var_export($spEntityId, true));
}
SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
'partial' => $partial
));
$dst = $spMetadata->getEndpointPrioritizedByBinding(
'SingleLogoutService',
array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST
)
);
$binding = \SAML2\Binding::getBinding($dst['Binding']);
if (isset($dst['ResponseLocation'])) {
$dst = $dst['ResponseLocation'];
} else {
$dst = $dst['Location'];
}
$lr->setDestination($dst);
$binding->send($lr);
}
/**
* Receive a logout message.
*
* @param SimpleSAML_IdP $idp The IdP we are receiving it for.
* @throws SimpleSAML_Error_BadRequest In case an error occurs while trying to receive the logout message.
*/
public static function receiveLogoutMessage(SimpleSAML_IdP $idp)
{
$binding = \SAML2\Binding::getCurrentBinding();
$message = $binding->receive();
$spEntityId = $message->getIssuer();
if ($spEntityId === null) {
/* Without an issuer we have no way to respond to the message. */
throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
}
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $message);
if ($message instanceof \SAML2\LogoutResponse) {
SimpleSAML\Logger::info('Received SAML 2.0 LogoutResponse from: '.var_export($spEntityId, true));
$statsData = array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
);
if (!$message->isSuccess()) {
$statsData['error'] = $message->getStatus();
}
SimpleSAML_Stats::log('saml:idp:LogoutResponse:recv', $statsData);
$relayState = $message->getRelayState();
if (!$message->isSuccess()) {
$logoutError = sspmod_saml_Message::getResponseError($message);
SimpleSAML\Logger::warning('Unsuccessful logout. Status was: '.$logoutError);
} else {
$logoutError = null;
}
$assocId = 'saml:'.$spEntityId;
$idp->handleLogoutResponse($assocId, $relayState, $logoutError);
} elseif ($message instanceof \SAML2\LogoutRequest) {
SimpleSAML\Logger::info('Received SAML 2.0 LogoutRequest from: '.var_export($spEntityId, true));
SimpleSAML_Stats::log('saml:idp:LogoutRequest:recv', array(
'spEntityID' => $spEntityId,
'idpEntityID' => $idpMetadata->getString('entityid'),
));
$spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
SimpleSAML\Logger::stats('saml20-idp-SLO spinit '.$spStatsId.' '.$idpMetadata->getString('entityid'));
$state = array(
'Responder' => array('sspmod_saml_IdP_SAML2', 'sendLogoutResponse'),
'saml:SPEntityId' => $spEntityId,
'saml:RelayState' => $message->getRelayState(),
'saml:RequestId' => $message->getId(),
);
$assocId = 'saml:'.$spEntityId;
$idp->handleLogoutRequest($state, $assocId);
} else {
throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: '.get_class($message));
}
}
/**
* Retrieve a logout URL for a given logout association.
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array $association The association that should be terminated.
* @param string|NULL $relayState An id that should be carried across the logout.
*/
public static function getLogoutURL(SimpleSAML_IdP $idp, array $association, $relayState) {
assert('is_string($relayState) || is_null($relayState)');
SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '. var_export($association['saml:entityID'], TRUE));
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
$bindings = array(\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST);
$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
if ($dst['Binding'] === \SAML2\Constants::BINDING_HTTP_POST) {
$params = array('association' => $association['id'], 'idp' => $idp->getId());
if ($relayState !== NULL) {
$params['RelayState'] = $relayState;
}
return SimpleSAML\Module::getModuleURL('core/idp/logout-iframe-post.php', $params);
}
$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
$lr->setDestination($dst['Location']);
$binding = new \SAML2\HTTPRedirect();
return $binding->getRedirectURL($lr);
}
/**
* Retrieve the metadata for the given SP association.
*
* @param SimpleSAML_IdP $idp The IdP the association belongs to.
* @param array $association The SP association.
* @return SimpleSAML_Configuration Configuration object for the SP metadata.
*/
public static function getAssociationConfig(SimpleSAML_IdP $idp, array $association) {
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
try {
return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
} catch (Exception $e) {
return SimpleSAML_Configuration::loadFromArray(array(), 'Unknown SAML 2 entity.');
}
}
/**
* Calculate the NameID value that should be used.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the SP.
* @param array &$state The authentication state of the user.
* @return string The NameID value.
*/
private static function generateNameIdValue(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, array &$state) {
$attribute = $spMetadata->getString('simplesaml.nameidattribute', NULL);
if ($attribute === NULL) {
$attribute = $idpMetadata->getString('simplesaml.nameidattribute', NULL);
if ($attribute === NULL) {
if (!isset($state['UserID'])) {
SimpleSAML\Logger::error('Unable to generate NameID. Check the userid.attribute option.');
}
$attributeValue = $state['UserID'];
$idpEntityId = $idpMetadata->getString('entityid');
$spEntityId = $spMetadata->getString('entityid');
$secretSalt = SimpleSAML\Utils\Config::getSecretSalt();
$uidData = 'uidhashbase' . $secretSalt;
$uidData .= strlen($idpEntityId) . ':' . $idpEntityId;
$uidData .= strlen($spEntityId) . ':' . $spEntityId;
$uidData .= strlen($attributeValue) . ':' . $attributeValue;
$uidData .= $secretSalt;
return hash('sha1', $uidData);
}
}
$attributes = $state['Attributes'];
if (!array_key_exists($attribute, $attributes)) {
SimpleSAML\Logger::error('Unable to add NameID: Missing ' . var_export($attribute, TRUE) .
' in the attributes of the user.');
return NULL;
}
return $attributes[$attribute][0];
}
/**
* Helper function for encoding attributes.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param array $attributes The attributes of the user
* @return array The encoded attributes.
*/
private static function encodeAttributes(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, array $attributes) {
$base64Attributes = $spMetadata->getBoolean('base64attributes', NULL);
if ($base64Attributes === NULL) {
$base64Attributes = $idpMetadata->getBoolean('base64attributes', FALSE);
}
if ($base64Attributes) {
$defaultEncoding = 'base64';
} else {
$defaultEncoding = 'string';
}
$srcEncodings = $idpMetadata->getArray('attributeencodings', array());
$dstEncodings = $spMetadata->getArray('attributeencodings', array());
/*
* Merge the two encoding arrays. Encodings specified in the target metadata
* takes precedence over the source metadata.
*/
$encodings = array_merge($srcEncodings, $dstEncodings);
$ret = array();
foreach ($attributes as $name => $values) {
$ret[$name] = array();
if (array_key_exists($name, $encodings)) {
$encoding = $encodings[$name];
} else {
$encoding = $defaultEncoding;
}
foreach ($values as $value) {
*
* @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
* @param array $association The association that should be terminated.
* @param string|NULL $relayState An id that should be carried across the logout.
*
* @return string The logout URL.
*/
public static function getLogoutURL(SimpleSAML_IdP $idp, array $association, $relayState)
{
assert('is_string($relayState) || is_null($relayState)');
SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $idp->getConfig();
$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
$bindings = array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST
);
$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
if ($dst['Binding'] === \SAML2\Constants::BINDING_HTTP_POST) {
$params = array('association' => $association['id'], 'idp' => $idp->getId());
if ($relayState !== null) {
$params['RelayState'] = $relayState;
}
return SimpleSAML\Module::getModuleURL('core/idp/logout-iframe-post.php', $params);
}
$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
$lr->setDestination($dst['Location']);
$binding = new \SAML2\HTTPRedirect();
return $binding->getRedirectURL($lr);
}
/**
* Retrieve the metadata for the given SP association.
*
* @param SimpleSAML_IdP $idp The IdP the association belongs to.
* @param array $association The SP association.
*
* @return SimpleSAML_Configuration Configuration object for the SP metadata.
*/
public static function getAssociationConfig(SimpleSAML_IdP $idp, array $association)
{
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
try {
return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
} catch (Exception $e) {
return SimpleSAML_Configuration::loadFromArray(array(), 'Unknown SAML 2 entity.');
}
}
/**
* Calculate the NameID value that should be used.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the SP.
* @param array &$state The authentication state of the user.
*
* @return string The NameID value.
*/
private static function generateNameIdValue(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
array &$state
) {
$attribute = $spMetadata->getString('simplesaml.nameidattribute', null);
if ($attribute === null) {
$attribute = $idpMetadata->getString('simplesaml.nameidattribute', null);
if ($attribute === null) {
if (!isset($state['UserID'])) {
SimpleSAML\Logger::error('Unable to generate NameID. Check the userid.attribute option.');
}
$attributeValue = $state['UserID'];
$idpEntityId = $idpMetadata->getString('entityid');
$spEntityId = $spMetadata->getString('entityid');
$secretSalt = SimpleSAML\Utils\Config::getSecretSalt();
$uidData = 'uidhashbase'.$secretSalt;
$uidData .= strlen($idpEntityId).':'.$idpEntityId;
$uidData .= strlen($spEntityId).':'.$spEntityId;
$uidData .= strlen($attributeValue).':'.$attributeValue;
$uidData .= $secretSalt;
return hash('sha1', $uidData);
}
}
$attributes = $state['Attributes'];
if (!array_key_exists($attribute, $attributes)) {
SimpleSAML\Logger::error('Unable to add NameID: Missing '.var_export($attribute, true).
' in the attributes of the user.');
return null;
}
return $attributes[$attribute][0];
}
/**
* Helper function for encoding attributes.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param array $attributes The attributes of the user.
*
* @return array The encoded attributes.
*
* @throws SimpleSAML_Error_Exception In case an unsupported encoding is specified by configuration.
*/
private static function encodeAttributes(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
array $attributes
) {
$base64Attributes = $spMetadata->getBoolean('base64attributes', null);
if ($base64Attributes === null) {
$base64Attributes = $idpMetadata->getBoolean('base64attributes', false);
}
if ($base64Attributes) {
$defaultEncoding = 'base64';
} else {
$defaultEncoding = 'string';
}
$srcEncodings = $idpMetadata->getArray('attributeencodings', array());
$dstEncodings = $spMetadata->getArray('attributeencodings', array());
/*
* Merge the two encoding arrays. Encodings specified in the target metadata
* takes precedence over the source metadata.
*/
$encodings = array_merge($srcEncodings, $dstEncodings);
$ret = array();
foreach ($attributes as $name => $values) {
$ret[$name] = array();
if (array_key_exists($name, $encodings)) {
$encoding = $encodings[$name];
} else {
$encoding = $defaultEncoding;
}
foreach ($values as $value) {
// allow null values
if ($value === null) {
$ret[$name][] = $value;
continue;
}
$attrval = $value;
if ($value instanceof DOMNodeList) {
$attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode);
}
switch ($encoding) {
case 'string':
$value = (string)$attrval;
break;
case 'base64':
$value = base64_encode((string)$attrval);
break;
case 'raw':
if (is_string($value)) {
$doc = \SAML2\DOMDocumentFactory::fromString('<root>' . $value . '</root>');
$value = $doc->firstChild->childNodes;
}
assert('$value instanceof DOMNodeList || $value instanceof \SAML2\XML\saml\NameID');
break;
default:
throw new SimpleSAML_Error_Exception('Invalid encoding for attribute ' .
var_export($name, TRUE) . ': ' . var_export($encoding, TRUE));
}
$ret[$name][] = $value;
}
}
return $ret;
}
/**
* Determine which NameFormat we should use for attributes.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @return string The NameFormat.
*/
private static function getAttributeNameFormat(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata) {
/* Try SP metadata first. */
$attributeNameFormat = $spMetadata->getString('attributes.NameFormat', NULL);
if ($attributeNameFormat !== NULL) {
return $attributeNameFormat;
}
$attributeNameFormat = $spMetadata->getString('AttributeNameFormat', NULL);
if ($attributeNameFormat !== NULL) {
return $attributeNameFormat;
}
/* Look in IdP metadata. */
$attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', NULL);
if ($attributeNameFormat !== NULL) {
return $attributeNameFormat;
}
$attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', NULL);
if ($attributeNameFormat !== NULL) {
return $attributeNameFormat;
}
/* Default. */
return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
}
/**
* Build an assertion based on information in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param array &$state The state array with information about the request.
* @return \SAML2\Assertion The assertion.
*/
private static function buildAssertion(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, array &$state) {
assert('isset($state["Attributes"])');
assert('isset($state["saml:ConsumerURL"])');
$now = time();
$signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', NULL);
if ($signAssertion === NULL) {
$signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', TRUE);
}
$config = SimpleSAML_Configuration::getInstance();
$a = new \SAML2\Assertion();
if ($signAssertion) {
sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $a);
}
$a->setIssuer($idpMetadata->getString('entityid'));
$a->setValidAudiences(array($spMetadata->getString('entityid')));
$a->setNotBefore($now - 30);
$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL);
if ($assertionLifetime === NULL) {
$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
}
$a->setNotOnOrAfter($now + $assertionLifetime);
if (isset($state['saml:AuthnContextClassRef'])) {
$a->setAuthnContext($state['saml:AuthnContextClassRef']);
} else {
$a->setAuthnContext(\SAML2\Constants::AC_PASSWORD);
}
$sessionStart = $now;
if (isset($state['AuthnInstant'])) {
$a->setAuthnInstant($state['AuthnInstant']);
$sessionStart = $state['AuthnInstant'];
}
$sessionLifetime = $config->getInteger('session.duration', 8*60*60);
$a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
$a->setSessionIndex(SimpleSAML\Utils\Random::generateID());
$sc = new \SAML2\XML\saml\SubjectConfirmation();
$sc->SubjectConfirmationData = new \SAML2\XML\saml\SubjectConfirmationData();
$sc->SubjectConfirmationData->NotOnOrAfter = $now + $assertionLifetime;
$sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
$sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
/* ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration. */
$hokAssertion = NULL;
if ($state['saml:Binding'] === \SAML2\Constants::BINDING_HOK_SSO) {
$hokAssertion = TRUE;
}
if ($hokAssertion === NULL) {
$hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', FALSE);
}
if ($hokAssertion) {
/* Holder-of-Key */
$sc->Method = \SAML2\Constants::CM_HOK;
if (\SimpleSAML\Utils\HTTP::isHTTPS()) {
if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
/* Extract certificate data (if this is a certificate). */
$clientCert = $_SERVER['SSL_CLIENT_CERT'];
$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
if (preg_match($pattern, $clientCert, $matches)) {
/* We have a client certificate from the browser which we add to the HoK assertion. */
$x509Certificate = new \SAML2\XML\ds\X509Certificate();
$x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
$x509Data = new \SAML2\XML\ds\X509Data();
$x509Data->data[] = $x509Certificate;
$keyInfo = new \SAML2\XML\ds\KeyInfo();
$keyInfo->info[] = $x509Data;
$sc->SubjectConfirmationData->info[] = $keyInfo;
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No valid client certificate provided during TLS handshake with IdP');
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No client certificate provided during TLS handshake with IdP');
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO');
} else {
/* Bearer */
$sc->Method = \SAML2\Constants::CM_BEARER;
}
$a->setSubjectConfirmation(array($sc));
/* Add attributes. */
if ($spMetadata->getBoolean('simplesaml.attributes', TRUE)) {
$attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata);
$a->setAttributeNameFormat($attributeNameFormat);
$attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']);
$a->setAttributes($attributes);
}
/* Generate the NameID for the assertion. */
if (isset($state['saml:NameIDFormat'])) {
$nameIdFormat = $state['saml:NameIDFormat'];
} else {
$nameIdFormat = NULL;
}
if ($nameIdFormat === NULL || !isset($state['saml:NameID'][$nameIdFormat])) {
/* Either not set in request, or not set to a format we supply. Fall back to old generation method. */
$nameIdFormat = $spMetadata->getString('NameIDFormat', NULL);
if ($nameIdFormat === NULL) {
$nameIdFormat = $idpMetadata->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT);
}
}
if (isset($state['saml:NameID'][$nameIdFormat])) {
$nameId = $state['saml:NameID'][$nameIdFormat];
$nameId['Format'] = $nameIdFormat;
} else {
$spNameQualifier = $spMetadata->getString('SPNameQualifier', NULL);
if ($spNameQualifier === NULL) {
$spNameQualifier = $spMetadata->getString('entityid');
}
if ($nameIdFormat === \SAML2\Constants::NAMEID_TRANSIENT) {
/* generate a random id */
$nameIdValue = SimpleSAML\Utils\Random::generateID();
} else {
/* this code will end up generating either a fixed assigned id (via nameid.attribute)
or random id if not assigned/configured */
$nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state);
if ($nameIdValue === NULL) {
SimpleSAML\Logger::warning('Falling back to transient NameID.');
$nameIdFormat = \SAML2\Constants::NAMEID_TRANSIENT;
$nameIdValue = SimpleSAML\Utils\Random::generateID();
}
}
$nameId = array(
'Format' => $nameIdFormat,
'Value' => $nameIdValue,
'SPNameQualifier' => $spNameQualifier,
);
}
$state['saml:idp:NameID'] = $nameId;
$a->setNameId($nameId);
$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL);
if ($encryptNameId === NULL) {
$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE);
}
if ($encryptNameId) {
$a->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
}
return $a;
}
/**
* Encrypt an assertion.
*
* This function takes in a \SAML2\Assertion and encrypts it if encryption of
* assertions are enabled in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param \SAML2\Assertion $assertion The assertion we are encrypting.
* @return \SAML2\Assertion|\SAML2\EncryptedAssertion The assertion.
*/
private static function encryptAssertion(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, \SAML2\Assertion $assertion) {
$encryptAssertion = $spMetadata->getBoolean('assertion.encryption', NULL);
if ($encryptAssertion === NULL) {
$encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', FALSE);
}
if (!$encryptAssertion) {
/* We are _not_ encrypting this assertion, and are therefore done. */
return $assertion;
}
$sharedKey = $spMetadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
} else {
$keys = $spMetadata->getPublicKeys('encryption', TRUE);
$key = $keys[0];
switch ($key['type']) {
case 'X509Certificate':
$pemKey = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($key['X509Certificate'], 64) .
"-----END CERTIFICATE-----\n";
break;
default:
throw new SimpleSAML_Error_Exception('Unsupported encryption key type: ' . $key['type']);
}
/* Extract the public key from the certificate for encryption. */
$key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type'=>'public'));
$key->loadKey($pemKey);
}
$ea = new \SAML2\EncryptedAssertion();
$ea->setAssertion($assertion, $key);
return $ea;
}
/**
* Build a logout request based on information in the metadata.
*
* @param SimpleSAML_Configuration idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration spMetadata The metadata of the SP.
* @param array $association The SP association.
* @param string|NULL $relayState An id that should be carried across the logout.
*/
private static function buildLogoutRequest(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, array $association, $relayState) {
$lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata);
$lr->setRelayState($relayState);
$lr->setSessionIndex($association['saml:SessionIndex']);
$lr->setNameId($association['saml:NameID']);
$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL);
if ($assertionLifetime === NULL) {
$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
}
$lr->setNotOnOrAfter(time() + $assertionLifetime);
$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL);
if ($encryptNameId === NULL) {
$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE);
}
if ($encryptNameId) {
$lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
}
return $lr;
}
/**
* Build a authentication response based on information in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param string $consumerURL The Destination URL of the response.
*/
private static function buildResponse(SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata, $consumerURL) {
$signResponse = $spMetadata->getBoolean('saml20.sign.response', NULL);
if ($signResponse === NULL) {
$signResponse = $idpMetadata->getBoolean('saml20.sign.response', TRUE);
}
$r = new \SAML2\Response();
$r->setIssuer($idpMetadata->getString('entityid'));
$r->setDestination($consumerURL);
if ($signResponse) {
sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $r);
}
return $r;
}
}
\ No newline at end of file
$attrval = $value;
if ($value instanceof DOMNodeList) {
$attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode);
}
switch ($encoding) {
case 'string':
$value = (string) $attrval;
break;
case 'base64':
$value = base64_encode((string) $attrval);
break;
case 'raw':
if (is_string($value)) {
$doc = \SAML2\DOMDocumentFactory::fromString('<root>'.$value.'</root>');
$value = $doc->firstChild->childNodes;
}
assert('$value instanceof DOMNodeList || $value instanceof \SAML2\XML\saml\NameID');
break;
default:
throw new SimpleSAML_Error_Exception('Invalid encoding for attribute '.
var_export($name, true).': '.var_export($encoding, true));
}
$ret[$name][] = $value;
}
}
return $ret;
}
/**
* Determine which NameFormat we should use for attributes.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
*
* @return string The NameFormat.
*/
private static function getAttributeNameFormat(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata
) {
// try SP metadata first
$attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null);
if ($attributeNameFormat !== null) {
return $attributeNameFormat;
}
$attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null);
if ($attributeNameFormat !== null) {
return $attributeNameFormat;
}
// look in IdP metadata
$attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null);
if ($attributeNameFormat !== null) {
return $attributeNameFormat;
}
$attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null);
if ($attributeNameFormat !== null) {
return $attributeNameFormat;
}
// default
return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
}
/**
* Build an assertion based on information in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param array &$state The state array with information about the request.
*
* @return \SAML2\Assertion The assertion.
*
* @throws SimpleSAML_Error_Exception In case an error occurs when creating a holder-of-key assertion.
*/
private static function buildAssertion(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
array &$state
) {
assert('isset($state["Attributes"])');
assert('isset($state["saml:ConsumerURL"])');
$now = time();
$signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null);
if ($signAssertion === null) {
$signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true);
}
$config = SimpleSAML_Configuration::getInstance();
$a = new \SAML2\Assertion();
if ($signAssertion) {
sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $a);
}
$a->setIssuer($idpMetadata->getString('entityid'));
$a->setValidAudiences(array($spMetadata->getString('entityid')));
$a->setNotBefore($now - 30);
$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
if ($assertionLifetime === null) {
$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
}
$a->setNotOnOrAfter($now + $assertionLifetime);
if (isset($state['saml:AuthnContextClassRef'])) {
$a->setAuthnContext($state['saml:AuthnContextClassRef']);
} else {
$a->setAuthnContext(\SAML2\Constants::AC_PASSWORD);
}
$sessionStart = $now;
if (isset($state['AuthnInstant'])) {
$a->setAuthnInstant($state['AuthnInstant']);
$sessionStart = $state['AuthnInstant'];
}
$sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60);
$a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
$a->setSessionIndex(SimpleSAML\Utils\Random::generateID());
$sc = new \SAML2\XML\saml\SubjectConfirmation();
$sc->SubjectConfirmationData = new \SAML2\XML\saml\SubjectConfirmationData();
$sc->SubjectConfirmationData->NotOnOrAfter = $now + $assertionLifetime;
$sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
$sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
// ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration
$hokAssertion = null;
if ($state['saml:Binding'] === \SAML2\Constants::BINDING_HOK_SSO) {
$hokAssertion = true;
}
if ($hokAssertion === null) {
$hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false);
}
if ($hokAssertion) {
// Holder-of-Key
$sc->Method = \SAML2\Constants::CM_HOK;
if (\SimpleSAML\Utils\HTTP::isHTTPS()) {
if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
// extract certificate data (if this is a certificate)
$clientCert = $_SERVER['SSL_CLIENT_CERT'];
$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
if (preg_match($pattern, $clientCert, $matches)) {
// we have a client certificate from the browser which we add to the HoK assertion
$x509Certificate = new \SAML2\XML\ds\X509Certificate();
$x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
$x509Data = new \SAML2\XML\ds\X509Data();
$x509Data->data[] = $x509Certificate;
$keyInfo = new \SAML2\XML\ds\KeyInfo();
$keyInfo->info[] = $x509Data;
$sc->SubjectConfirmationData->info[] = $keyInfo;
} else {
throw new SimpleSAML_Error_Exception(
'Error creating HoK assertion: No valid client certificate provided during TLS handshake '.
'with IdP'
);
}
} else {
throw new SimpleSAML_Error_Exception(
'Error creating HoK assertion: No client certificate provided during TLS handshake with IdP'
);
}
} else {
throw new SimpleSAML_Error_Exception(
'Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO'
);
}
} else {
// Bearer
$sc->Method = \SAML2\Constants::CM_BEARER;
}
$a->setSubjectConfirmation(array($sc));
// add attributes
if ($spMetadata->getBoolean('simplesaml.attributes', true)) {
$attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata);
$a->setAttributeNameFormat($attributeNameFormat);
$attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']);
$a->setAttributes($attributes);
}
// generate the NameID for the assertion
if (isset($state['saml:NameIDFormat'])) {
$nameIdFormat = $state['saml:NameIDFormat'];
} else {
$nameIdFormat = null;
}
if ($nameIdFormat === null || !isset($state['saml:NameID'][$nameIdFormat])) {
// either not set in request, or not set to a format we supply. Fall back to old generation method
$nameIdFormat = $spMetadata->getString('NameIDFormat', null);
if ($nameIdFormat === null) {
$nameIdFormat = $idpMetadata->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT);
}
}
if (isset($state['saml:NameID'][$nameIdFormat])) {
$nameId = $state['saml:NameID'][$nameIdFormat];
$nameId['Format'] = $nameIdFormat;
} else {
$spNameQualifier = $spMetadata->getString('SPNameQualifier', null);
if ($spNameQualifier === null) {
$spNameQualifier = $spMetadata->getString('entityid');
}
if ($nameIdFormat === \SAML2\Constants::NAMEID_TRANSIENT) {
// generate a random id
$nameIdValue = SimpleSAML\Utils\Random::generateID();
} else {
/* this code will end up generating either a fixed assigned id (via nameid.attribute)
or random id if not assigned/configured */
$nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state);
if ($nameIdValue === null) {
SimpleSAML\Logger::warning('Falling back to transient NameID.');
$nameIdFormat = \SAML2\Constants::NAMEID_TRANSIENT;
$nameIdValue = SimpleSAML\Utils\Random::generateID();
}
}
$nameId = array(
'Format' => $nameIdFormat,
'Value' => $nameIdValue,
'SPNameQualifier' => $spNameQualifier,
);
}
$state['saml:idp:NameID'] = $nameId;
$a->setNameId($nameId);
$encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
if ($encryptNameId === null) {
$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
}
if ($encryptNameId) {
$a->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
}
return $a;
}
/**
* Encrypt an assertion.
*
* This function takes in a \SAML2\Assertion and encrypts it if encryption of
* assertions are enabled in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param \SAML2\Assertion $assertion The assertion we are encrypting.
*
* @return \SAML2\Assertion|\SAML2\EncryptedAssertion The assertion.
*
* @throws SimpleSAML_Error_Exception In case the encryption key type is not supported.
*/
private static function encryptAssertion(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
\SAML2\Assertion $assertion
) {
$encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null);
if ($encryptAssertion === null) {
$encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false);
}
if (!$encryptAssertion) {
// we are _not_ encrypting this assertion, and are therefore done
return $assertion;
}
$sharedKey = $spMetadata->getString('sharedkey', null);
if ($sharedKey !== null) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
} else {
$keys = $spMetadata->getPublicKeys('encryption', true);
$key = $keys[0];
switch ($key['type']) {
case 'X509Certificate':
$pemKey = "-----BEGIN CERTIFICATE-----\n".
chunk_split($key['X509Certificate'], 64).
"-----END CERTIFICATE-----\n";
break;
default:
throw new SimpleSAML_Error_Exception('Unsupported encryption key type: '.$key['type']);
}
// extract the public key from the certificate for encryption
$key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public'));
$key->loadKey($pemKey);
}
$ea = new \SAML2\EncryptedAssertion();
$ea->setAssertion($assertion, $key);
return $ea;
}
/**
* Build a logout request based on information in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param array $association The SP association.
* @param string|null $relayState An id that should be carried across the logout.
*
* @return \SAML2\LogoutResponse The corresponding SAML2 logout response.
*/
private static function buildLogoutRequest(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
array $association,
$relayState
) {
$lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata);
$lr->setRelayState($relayState);
$lr->setSessionIndex($association['saml:SessionIndex']);
$lr->setNameId($association['saml:NameID']);
$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
if ($assertionLifetime === null) {
$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
}
$lr->setNotOnOrAfter(time() + $assertionLifetime);
$encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
if ($encryptNameId === null) {
$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
}
if ($encryptNameId) {
$lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
}
return $lr;
}
/**
* Build a authentication response based on information in the metadata.
*
* @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
* @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
* @param string $consumerURL The Destination URL of the response.
*
* @return \SAML2\Response The SAML2 response corresponding to the given data.
*/
private static function buildResponse(
SimpleSAML_Configuration $idpMetadata,
SimpleSAML_Configuration $spMetadata,
$consumerURL
) {
$signResponse = $spMetadata->getBoolean('saml20.sign.response', null);
if ($signResponse === null) {
$signResponse = $idpMetadata->getBoolean('saml20.sign.response', true);
}
$r = new \SAML2\Response();
$r->setIssuer($idpMetadata->getString('entityid'));
$r->setDestination($consumerURL);
if ($signResponse) {
sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $r);
}
return $r;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment