diff --git a/modules/saml/hooks/hook_metadata_hosted.php b/modules/saml/hooks/hook_metadata_hosted.php index 510a8133d9f270e876f25570ed2310009836746b..f94d19ff7b37c19012f4a56c3e91fc74d2193ae0 100644 --- a/modules/saml/hooks/hook_metadata_hosted.php +++ b/modules/saml/hooks/hook_metadata_hosted.php @@ -6,7 +6,8 @@ * @param array &$metadataHosted The metadata links for hosted metadata on the frontpage. */ -function saml_hook_metadata_hosted(&$metadataHosted) { +function saml_hook_metadata_hosted(&$metadataHosted) +{ assert(is_array($metadataHosted)); $sources = SimpleSAML_Auth_Source::getSourcesOfType('saml:SP'); diff --git a/modules/saml/lib/BaseNameIDGenerator.php b/modules/saml/lib/BaseNameIDGenerator.php index 342c51d6619f78386fe8c96bdc1ef05bd0b1d35f..cea1c219688933cf1115bca0641a7f95dfe876f7 100644 --- a/modules/saml/lib/BaseNameIDGenerator.php +++ b/modules/saml/lib/BaseNameIDGenerator.php @@ -5,112 +5,114 @@ * * @package SimpleSAMLphp */ -abstract class sspmod_saml_BaseNameIDGenerator extends SimpleSAML_Auth_ProcessingFilter { - - /** - * What NameQualifier should be used. - * Can be one of: - * - a string: The qualifier to use. - * - FALSE: Do not include a NameQualifier. This is the default. - * - TRUE: Use the IdP entity ID. - * - * @var string|bool - */ - private $nameQualifier; - - - /** - * What SPNameQualifier should be used. - * Can be one of: - * - a string: The qualifier to use. - * - FALSE: Do not include a SPNameQualifier. - * - TRUE: Use the SP entity ID. This is the default. - * - * @var string|bool - */ - private $spNameQualifier; - - - /** - * The format of this NameID. - * - * This property must be initialized the subclass. - * - * @var string - */ - protected $format; - - - /** - * Initialize this filter, parse configuration. - * - * @param array $config Configuration information about this filter. - * @param mixed $reserved For future use. - */ - public function __construct($config, $reserved) { - parent::__construct($config, $reserved); - assert(is_array($config)); - - if (isset($config['NameQualifier'])) { - $this->nameQualifier = $config['NameQualifier']; - } else { - $this->nameQualifier = FALSE; - } - - if (isset($config['SPNameQualifier'])) { - $this->spNameQualifier = $config['SPNameQualifier']; - } else { - $this->spNameQualifier = TRUE; - } - } - - - /** - * Get the NameID value. - * - * @return string|NULL The NameID value. - */ - abstract protected function getValue(array &$state); - - - /** - * Generate transient NameID. - * - * @param array &$state The request state. - */ - public function process(&$state) { - assert(is_array($state)); - assert(is_string($this->format)); - - $value = $this->getValue($state); - if ($value === NULL) { - return; - } - - $nameId = new \SAML2\XML\saml\NameID(); - $nameId->value = $value; - - if ($this->nameQualifier === TRUE) { - if (isset($state['IdPMetadata']['entityid'])) { - $nameId->NameQualifier = $state['IdPMetadata']['entityid']; - } else { - SimpleSAML\Logger::warning('No IdP entity ID, unable to set NameQualifier.'); - } - } elseif (is_string($this->nameQualifier)) { - $nameId->NameQualifier = $this->nameQualifier; - } - - if ($this->spNameQualifier === TRUE) { - if (isset($state['SPMetadata']['entityid'])) { - $nameId->SPNameQualifier = $state['SPMetadata']['entityid']; - } else { - SimpleSAML\Logger::warning('No SP entity ID, unable to set SPNameQualifier.'); - } - } elseif (is_string($this->spNameQualifier)) { - $nameId->SPNameQualifier = $this->spNameQualifier; - } - - $state['saml:NameID'][$this->format] = $nameId; - } +abstract class sspmod_saml_BaseNameIDGenerator extends SimpleSAML_Auth_ProcessingFilter +{ + /** + * What NameQualifier should be used. + * Can be one of: + * - a string: The qualifier to use. + * - FALSE: Do not include a NameQualifier. This is the default. + * - TRUE: Use the IdP entity ID. + * + * @var string|bool + */ + private $nameQualifier; + + + /** + * What SPNameQualifier should be used. + * Can be one of: + * - a string: The qualifier to use. + * - FALSE: Do not include a SPNameQualifier. + * - TRUE: Use the SP entity ID. This is the default. + * + * @var string|bool + */ + private $spNameQualifier; + + + /** + * The format of this NameID. + * + * This property must be initialized the subclass. + * + * @var string + */ + protected $format; + + + /** + * Initialize this filter, parse configuration. + * + * @param array $config Configuration information about this filter. + * @param mixed $reserved For future use. + */ + public function __construct($config, $reserved) + { + parent::__construct($config, $reserved); + assert(is_array($config)); + + if (isset($config['NameQualifier'])) { + $this->nameQualifier = $config['NameQualifier']; + } else { + $this->nameQualifier = false; + } + + if (isset($config['SPNameQualifier'])) { + $this->spNameQualifier = $config['SPNameQualifier']; + } else { + $this->spNameQualifier = true; + } + } + + + /** + * Get the NameID value. + * + * @return string|null The NameID value. + */ + abstract protected function getValue(array &$state); + + + /** + * Generate transient NameID. + * + * @param array &$state The request state. + */ + public function process(&$state) + { + assert(is_array($state)); + assert(is_string($this->format)); + + $value = $this->getValue($state); + if ($value === null) { + return; + } + + $nameId = new \SAML2\XML\saml\NameID(); + $nameId->value = $value; + + if ($this->nameQualifier === true) { + if (isset($state['IdPMetadata']['entityid'])) { + $nameId->NameQualifier = $state['IdPMetadata']['entityid']; + } else { + SimpleSAML\Logger::warning('No IdP entity ID, unable to set NameQualifier.'); + } + } elseif (is_string($this->nameQualifier)) { + $nameId->NameQualifier = $this->nameQualifier; + } + + if ($this->spNameQualifier === true) { + if (isset($state['SPMetadata']['entityid'])) { + $nameId->SPNameQualifier = $state['SPMetadata']['entityid']; + } else { + SimpleSAML\Logger::warning('No SP entity ID, unable to set SPNameQualifier.'); + } + } elseif (is_string($this->spNameQualifier)) { + $nameId->SPNameQualifier = $this->spNameQualifier; + } + + $state['saml:NameID'][$this->format] = $nameId; + } } diff --git a/modules/saml/lib/Error.php b/modules/saml/lib/Error.php index 0a28d6b6108b3702247bf160470e0f54fa115d40..74f9ce873ccb57807172a36a0e8e2c18020425e1 100644 --- a/modules/saml/lib/Error.php +++ b/modules/saml/lib/Error.php @@ -7,190 +7,188 @@ */ class sspmod_saml_Error extends SimpleSAML_Error_Exception { - /** - * The top-level status code. - * - * @var string - */ - private $status; - - - /** - * The second-level status code, or NULL if no second-level status code is defined. - * - * @var string|NULL - */ - private $subStatus; - - - /** - * The status message, or NULL if no status message is defined. - * - * @var string|NULL - */ - private $statusMessage; - - - /** - * Create a SAML 2 error. - * - * @param string $status The top-level status code. - * @param string|NULL $subStatus The second-level status code. Can be NULL, in which case there is no second-level status code. - * @param string|NULL $statusMessage The status message. Can be NULL, in which case there is no status message. - * @param Exception|NULL $cause The cause of this exception. Can be NULL. - */ - public function __construct($status, $subStatus = null, $statusMessage = null, Exception $cause = null) + /** + * The top-level status code. + * + * @var string + */ + private $status; + + /** + * The second-level status code, or NULL if no second-level status code is defined. + * + * @var string|null + */ + private $subStatus; + + /** + * The status message, or NULL if no status message is defined. + * + * @var string|null + */ + private $statusMessage; + + + /** + * Create a SAML 2 error. + * + * @param string $status The top-level status code. + * @param string|null $subStatus The second-level status code. Can be NULL, in which case there is no second-level status code. + * @param string|null $statusMessage The status message. Can be NULL, in which case there is no status message. + * @param Exception|null $cause The cause of this exception. Can be NULL. + */ + public function __construct($status, $subStatus = null, $statusMessage = null, Exception $cause = null) { - assert(is_string($status)); - assert($subStatus === null || is_string($subStatus)); - assert($statusMessage === null || is_string($statusMessage)); - - $st = self::shortStatus($status); - if ($subStatus !== null) { - $st .= '/' . self::shortStatus($subStatus); - } - if ($statusMessage !== null) { - $st .= ': ' . $statusMessage; - } - parent::__construct($st, 0, $cause); - - $this->status = $status; - $this->subStatus = $subStatus; - $this->statusMessage = $statusMessage; - } - - - /** - * Get the top-level status code. - * - * @return string The top-level status code. - */ - public function getStatus() + assert(is_string($status)); + assert($subStatus === null || is_string($subStatus)); + assert($statusMessage === null || is_string($statusMessage)); + + $st = self::shortStatus($status); + if ($subStatus !== null) { + $st .= '/' . self::shortStatus($subStatus); + } + if ($statusMessage !== null) { + $st .= ': ' . $statusMessage; + } + parent::__construct($st, 0, $cause); + + $this->status = $status; + $this->subStatus = $subStatus; + $this->statusMessage = $statusMessage; + } + + + /** + * Get the top-level status code. + * + * @return string The top-level status code. + */ + public function getStatus() { - return $this->status; - } + return $this->status; + } - /** - * Get the second-level status code. - * - * @return string|NULL The second-level status code or NULL if no second-level status code is present. - */ - public function getSubStatus() + /** + * Get the second-level status code. + * + * @return string|null The second-level status code or NULL if no second-level status code is present. + */ + public function getSubStatus() { - return $this->subStatus; - } + return $this->subStatus; + } - /** - * Get the status message. - * - * @return string|NULL The status message or NULL if no status message is present. - */ - public function getStatusMessage() + /** + * Get the status message. + * + * @return string|null The status message or NULL if no status message is present. + */ + public function getStatusMessage() { - return $this->statusMessage; - } - - - /** - * Create a SAML2 error from an exception. - * - * This function attempts to create a SAML2 error with the appropriate - * status codes from an arbitrary exception. - * - * @param Exception $exception The original exception. - * @return sspmod_saml_Error The new exception. - */ - public static function fromException(Exception $exception) + return $this->statusMessage; + } + + + /** + * Create a SAML2 error from an exception. + * + * This function attempts to create a SAML2 error with the appropriate + * status codes from an arbitrary exception. + * + * @param Exception $exception The original exception. + * @return sspmod_saml_Error The new exception. + */ + public static function fromException(Exception $exception) { - if ($exception instanceof sspmod_saml_Error) { - // Return the original exception unchanged - return $exception; - - // TODO: remove this branch in 2.0 - } elseif ($exception instanceof SimpleSAML_Error_NoPassive) { - $e = new self( - \SAML2\Constants::STATUS_RESPONDER, - \SAML2\Constants::STATUS_NO_PASSIVE, - $exception->getMessage(), - $exception - ); - // TODO: remove this branch in 2.0 - } elseif ($exception instanceof SimpleSAML_Error_ProxyCountExceeded) { - $e = new self( - \SAML2\Constants::STATUS_RESPONDER, - \SAML2\Constants::STATUS_PROXY_COUNT_EXCEEDED, - $exception->getMessage(), - $exception - ); - } else { - $e = new self( - \SAML2\Constants::STATUS_RESPONDER, - null, - get_class($exception) . ': ' . $exception->getMessage(), - $exception - ); - } - - return $e; - } - - - /** - * Create a normal exception from a SAML2 error. - * - * This function attempts to reverse the operation of the fromException() function. - * If it is unable to create a more specific exception, it will return the current - * object. - * - * @see sspmod_saml_Error::fromException() - * - * @return SimpleSAML_Error_Exception An exception representing this error. - */ - public function toException() - { - $e = null; - - switch ($this->status) { - case \SAML2\Constants::STATUS_RESPONDER: - switch ($this->subStatus) { - case \SAML2\Constants::STATUS_NO_PASSIVE: - $e = new SimpleSAML\Module\saml\Error\NoPassive( - \SAML2\Constants::STATUS_RESPONDER, - $this->statusMessage + if ($exception instanceof sspmod_saml_Error) { + // Return the original exception unchanged + return $exception; + + // TODO: remove this branch in 2.0 + } elseif ($exception instanceof SimpleSAML_Error_NoPassive) { + $e = new self( + \SAML2\Constants::STATUS_RESPONDER, + \SAML2\Constants::STATUS_NO_PASSIVE, + $exception->getMessage(), + $exception + ); + // TODO: remove this branch in 2.0 + } elseif ($exception instanceof SimpleSAML_Error_ProxyCountExceeded) { + $e = new self( + \SAML2\Constants::STATUS_RESPONDER, + \SAML2\Constants::STATUS_PROXY_COUNT_EXCEEDED, + $exception->getMessage(), + $exception + ); + } else { + $e = new self( + \SAML2\Constants::STATUS_RESPONDER, + null, + get_class($exception) . ': ' . $exception->getMessage(), + $exception ); - break; - } - break; - } - - if ($e === null) { - return $this; - } - - return $e; - } - - - /** - * Create a short version of the status code. - * - * Remove the 'urn:oasis:names:tc:SAML:2.0:status:'-prefix of status codes - * if it is present. - * - * @param string $status The status code. - * @return string A shorter version of the status code. - */ - private static function shortStatus($status) + } + + return $e; + } + + + /** + * Create a normal exception from a SAML2 error. + * + * This function attempts to reverse the operation of the fromException() function. + * If it is unable to create a more specific exception, it will return the current + * object. + * + * @see sspmod_saml_Error::fromException() + * + * @return SimpleSAML_Error_Exception An exception representing this error. + */ + public function toException() + { + $e = null; + + switch ($this->status) { + case \SAML2\Constants::STATUS_RESPONDER: + switch ($this->subStatus) { + case \SAML2\Constants::STATUS_NO_PASSIVE: + $e = new SimpleSAML\Module\saml\Error\NoPassive( + \SAML2\Constants::STATUS_RESPONDER, + $this->statusMessage + ); + break; + } + break; + } + + if ($e === null) { + return $this; + } + + return $e; + } + + + /** + * Create a short version of the status code. + * + * Remove the 'urn:oasis:names:tc:SAML:2.0:status:'-prefix of status codes + * if it is present. + * + * @param string $status The status code. + * @return string A shorter version of the status code. + */ + private static function shortStatus($status) { - assert(is_string($status)); + assert(is_string($status)); - $t = 'urn:oasis:names:tc:SAML:2.0:status:'; - if (substr($status, 0, strlen($t)) === $t) { - return substr($status, strlen($t)); - } + $t = 'urn:oasis:names:tc:SAML:2.0:status:'; + if (substr($status, 0, strlen($t)) === $t) { + return substr($status, strlen($t)); + } - return $status; - } + return $status; + } } diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php index 68fb13ed84fbbc944cdedc4f8869789beeab0503..fcd3603ce6354ceecb693f307eecc8a3d0ff740d 100644 --- a/modules/saml/lib/IdP/SAML1.php +++ b/modules/saml/lib/IdP/SAML1.php @@ -110,7 +110,7 @@ class sspmod_saml_IdP_SAML1 if (!$found) { throw new Exception('Invalid AssertionConsumerService for SP ' . var_export($spEntityId, true) . ': ' . var_export($shire, true)); - } + } SimpleSAML_Stats::log('saml:idp:AuthnRequest', array( 'spEntityID' => $spEntityId, diff --git a/modules/saml/lib/SP/LogoutStore.php b/modules/saml/lib/SP/LogoutStore.php index a92aeadf3ee190b96a2291165f0d9e541853db85..d4a4004509d39e495155a118447325946422f326 100644 --- a/modules/saml/lib/SP/LogoutStore.php +++ b/modules/saml/lib/SP/LogoutStore.php @@ -7,297 +7,297 @@ */ class sspmod_saml_SP_LogoutStore { - /** - * Create logout table in SQL, if it is missing. - * - * @param \SimpleSAML\Store\SQL $store The datastore. - */ - private static function createLogoutTable(\SimpleSAML\Store\SQL $store) + /** + * Create logout table in SQL, if it is missing. + * + * @param \SimpleSAML\Store\SQL $store The datastore. + */ + private static function createLogoutTable(\SimpleSAML\Store\SQL $store) { - $tableVer = $store->getTableVersion('saml_LogoutStore'); - if ($tableVer === 2) { - return; - } elseif ($tableVer === 1) { - /* TableVersion 2 increased the column size to 255 which is the maximum length of a FQDN. */ - $query = 'ALTER TABLE ' . $store->prefix . '_saml_LogoutStore MODIFY _authSource VARCHAR(255) NOT NULL'; - try { - $store->pdo->exec($query); - } catch (Exception $e) { - SimpleSAML\Logger::warning($store->pdo->errorInfo()); - return; - } - $store->setTableVersion('saml_LogoutStore', 2); - return; - } - - $query = 'CREATE TABLE ' . $store->prefix . '_saml_LogoutStore ( - _authSource VARCHAR(255) NOT NULL, - _nameId VARCHAR(40) NOT NULL, - _sessionIndex VARCHAR(50) NOT NULL, - _expire TIMESTAMP NOT NULL, - _sessionId VARCHAR(50) NOT NULL, - UNIQUE (_authSource, _nameID, _sessionIndex) - )'; - $store->pdo->exec($query); - - $query = 'CREATE INDEX ' . $store->prefix . '_saml_LogoutStore_expire ON ' . $store->prefix . '_saml_LogoutStore (_expire)'; - $store->pdo->exec($query); - - $query = 'CREATE INDEX ' . $store->prefix . '_saml_LogoutStore_nameId ON ' . $store->prefix . '_saml_LogoutStore (_authSource, _nameId)'; - $store->pdo->exec($query); - - $store->setTableVersion('saml_LogoutStore', 2); - } - - - /** - * Clean the logout table of expired entries. - * - * @param \SimpleSAML\Store\SQL $store The datastore. - */ - private static function cleanLogoutStore(\SimpleSAML\Store\SQL $store) + $tableVer = $store->getTableVersion('saml_LogoutStore'); + if ($tableVer === 2) { + return; + } elseif ($tableVer === 1) { + /* TableVersion 2 increased the column size to 255 which is the maximum length of a FQDN. */ + $query = 'ALTER TABLE ' . $store->prefix . '_saml_LogoutStore MODIFY _authSource VARCHAR(255) NOT NULL'; + try { + $store->pdo->exec($query); + } catch (Exception $e) { + SimpleSAML\Logger::warning($store->pdo->errorInfo()); + return; + } + $store->setTableVersion('saml_LogoutStore', 2); + return; + } + + $query = 'CREATE TABLE ' . $store->prefix . '_saml_LogoutStore ( + _authSource VARCHAR(255) NOT NULL, + _nameId VARCHAR(40) NOT NULL, + _sessionIndex VARCHAR(50) NOT NULL, + _expire TIMESTAMP NOT NULL, + _sessionId VARCHAR(50) NOT NULL, + UNIQUE (_authSource, _nameID, _sessionIndex) + )'; + $store->pdo->exec($query); + + $query = 'CREATE INDEX ' . $store->prefix . '_saml_LogoutStore_expire ON ' . $store->prefix . '_saml_LogoutStore (_expire)'; + $store->pdo->exec($query); + + $query = 'CREATE INDEX ' . $store->prefix . '_saml_LogoutStore_nameId ON ' . $store->prefix . '_saml_LogoutStore (_authSource, _nameId)'; + $store->pdo->exec($query); + + $store->setTableVersion('saml_LogoutStore', 2); + } + + + /** + * Clean the logout table of expired entries. + * + * @param \SimpleSAML\Store\SQL $store The datastore. + */ + private static function cleanLogoutStore(\SimpleSAML\Store\SQL $store) { - SimpleSAML\Logger::debug('saml.LogoutStore: Cleaning logout store.'); + SimpleSAML\Logger::debug('saml.LogoutStore: Cleaning logout store.'); - $query = 'DELETE FROM ' . $store->prefix . '_saml_LogoutStore WHERE _expire < :now'; - $params = array('now' => gmdate('Y-m-d H:i:s')); + $query = 'DELETE FROM ' . $store->prefix . '_saml_LogoutStore WHERE _expire < :now'; + $params = array('now' => gmdate('Y-m-d H:i:s')); - $query = $store->pdo->prepare($query); - $query->execute($params); - } + $query = $store->pdo->prepare($query); + $query->execute($params); + } - /** - * Register a session in the SQL datastore. - * - * @param \SimpleSAML\Store\SQL $store The datastore. - * @param string $authId The authsource ID. - * @param string $nameId The hash of the users NameID. - * @param string $sessionIndex The SessionIndex of the user. - */ - private static function addSessionSQL(\SimpleSAML\Store\SQL $store, $authId, $nameId, $sessionIndex, $expire, $sessionId) + /** + * Register a session in the SQL datastore. + * + * @param \SimpleSAML\Store\SQL $store The datastore. + * @param string $authId The authsource ID. + * @param string $nameId The hash of the users NameID. + * @param string $sessionIndex The SessionIndex of the user. + */ + private static function addSessionSQL(\SimpleSAML\Store\SQL $store, $authId, $nameId, $sessionIndex, $expire, $sessionId) { - assert(is_string($authId)); - assert(is_string($nameId)); - assert(is_string($sessionIndex)); - assert(is_string($sessionId)); - assert(is_int($expire)); - - self::createLogoutTable($store); - - if (rand(0, 1000) < 10) { - self::cleanLogoutStore($store); - } - - $data = array( - '_authSource' => $authId, - '_nameId' => $nameId, - '_sessionIndex' => $sessionIndex, - '_expire' => gmdate('Y-m-d H:i:s', $expire), - '_sessionId' => $sessionId, - ); - $store->insertOrUpdate($store->prefix . '_saml_LogoutStore', array('_authSource', '_nameId', '_sessionIndex'), $data); - } - - - /** - * Retrieve sessions from the SQL datastore. - * - * @param \SimpleSAML\Store\SQL $store The datastore. - * @param string $authId The authsource ID. - * @param string $nameId The hash of the users NameID. - * @return array Associative array of SessionIndex => SessionId. - */ - private static function getSessionsSQL(\SimpleSAML\Store\SQL $store, $authId, $nameId) + assert(is_string($authId)); + assert(is_string($nameId)); + assert(is_string($sessionIndex)); + assert(is_string($sessionId)); + assert(is_int($expire)); + + self::createLogoutTable($store); + + if (rand(0, 1000) < 10) { + self::cleanLogoutStore($store); + } + + $data = array( + '_authSource' => $authId, + '_nameId' => $nameId, + '_sessionIndex' => $sessionIndex, + '_expire' => gmdate('Y-m-d H:i:s', $expire), + '_sessionId' => $sessionId, + ); + $store->insertOrUpdate($store->prefix . '_saml_LogoutStore', array('_authSource', '_nameId', '_sessionIndex'), $data); + } + + + /** + * Retrieve sessions from the SQL datastore. + * + * @param \SimpleSAML\Store\SQL $store The datastore. + * @param string $authId The authsource ID. + * @param string $nameId The hash of the users NameID. + * @return array Associative array of SessionIndex => SessionId. + */ + private static function getSessionsSQL(\SimpleSAML\Store\SQL $store, $authId, $nameId) { - assert(is_string($authId)); - assert(is_string($nameId)); - - self::createLogoutTable($store); - - $params = array( - '_authSource' => $authId, - '_nameId' => $nameId, - 'now' => gmdate('Y-m-d H:i:s'), - ); - - // We request the columns in lowercase in order to be compatible with PostgreSQL - $query = 'SELECT _sessionIndex AS _sessionindex, _sessionId AS _sessionid FROM ' . $store->prefix . '_saml_LogoutStore' . - ' WHERE _authSource = :_authSource AND _nameId = :_nameId AND _expire >= :now'; - $query = $store->pdo->prepare($query); - $query->execute($params); - - $res = array(); - while ( ($row = $query->fetch(PDO::FETCH_ASSOC)) !== false) { - $res[$row['_sessionindex']] = $row['_sessionid']; - } - - return $res; - } - - - /** - * Retrieve all session IDs from a key-value store. - * - * @param \SimpleSAML\Store $store The datastore. - * @param string $authId The authsource ID. - * @param string $nameId The hash of the users NameID. - * @param array $sessionIndexes The session indexes. - * @return array Associative array of SessionIndex => SessionId. - */ - private static function getSessionsStore(\SimpleSAML\Store $store, $authId, $nameId, array $sessionIndexes) + assert(is_string($authId)); + assert(is_string($nameId)); + + self::createLogoutTable($store); + + $params = array( + '_authSource' => $authId, + '_nameId' => $nameId, + 'now' => gmdate('Y-m-d H:i:s'), + ); + + // We request the columns in lowercase in order to be compatible with PostgreSQL + $query = 'SELECT _sessionIndex AS _sessionindex, _sessionId AS _sessionid FROM ' . $store->prefix . '_saml_LogoutStore' . + ' WHERE _authSource = :_authSource AND _nameId = :_nameId AND _expire >= :now'; + $query = $store->pdo->prepare($query); + $query->execute($params); + + $res = array(); + while ( ($row = $query->fetch(PDO::FETCH_ASSOC)) !== false) { + $res[$row['_sessionindex']] = $row['_sessionid']; + } + + return $res; + } + + + /** + * Retrieve all session IDs from a key-value store. + * + * @param \SimpleSAML\Store $store The datastore. + * @param string $authId The authsource ID. + * @param string $nameId The hash of the users NameID. + * @param array $sessionIndexes The session indexes. + * @return array Associative array of SessionIndex => SessionId. + */ + private static function getSessionsStore(\SimpleSAML\Store $store, $authId, $nameId, array $sessionIndexes) { - assert(is_string($authId)); - assert(is_string($nameId)); - - $res = array(); - foreach ($sessionIndexes as $sessionIndex) { - $sessionId = $store->get('saml.LogoutStore', $nameId . ':' . $sessionIndex); - if ($sessionId === null) { - continue; - } - assert(is_string($sessionId)); - $res[$sessionIndex] = $sessionId; - } - - return $res; - } - - - /** - * Register a new session in the datastore. - * - * Please observe the change of the signature in this method. Previously, the second parameter ($nameId) was forced - * to be an array. However, it has no type restriction now, and the documentation states it must be a - * \SAML2\XML\saml\NameID object. Currently, this function still accepts an array passed as $nameId, and will - * silently convert it to a \SAML2\XML\saml\NameID object. This is done to keep backwards-compatibility, though will - * no longer be possible in the future as the $nameId parameter will be required to be an object. - * - * @param string $authId The authsource ID. - * @param \SAML2\XML\saml\NameID $nameId The NameID of the user. - * @param string|NULL $sessionIndex The SessionIndex of the user. - */ - public static function addSession($authId, $nameId, $sessionIndex, $expire) + assert(is_string($authId)); + assert(is_string($nameId)); + + $res = array(); + foreach ($sessionIndexes as $sessionIndex) { + $sessionId = $store->get('saml.LogoutStore', $nameId . ':' . $sessionIndex); + if ($sessionId === null) { + continue; + } + assert(is_string($sessionId)); + $res[$sessionIndex] = $sessionId; + } + + return $res; + } + + + /** + * Register a new session in the datastore. + * + * Please observe the change of the signature in this method. Previously, the second parameter ($nameId) was forced + * to be an array. However, it has no type restriction now, and the documentation states it must be a + * \SAML2\XML\saml\NameID object. Currently, this function still accepts an array passed as $nameId, and will + * silently convert it to a \SAML2\XML\saml\NameID object. This is done to keep backwards-compatibility, though will + * no longer be possible in the future as the $nameId parameter will be required to be an object. + * + * @param string $authId The authsource ID. + * @param \SAML2\XML\saml\NameID $nameId The NameID of the user. + * @param string|null $sessionIndex The SessionIndex of the user. + */ + public static function addSession($authId, $nameId, $sessionIndex, $expire) { - assert(is_string($authId)); - assert(is_string($sessionIndex) || $sessionIndex === null); - assert(is_int($expire)); - - if ($sessionIndex === null) { - /* This IdP apparently did not include a SessionIndex, and thus probably does not - * support SLO. We still want to add the session to the data store just in case - * it supports SLO, but we don't want an LogoutRequest with a specific - * SessionIndex to match this session. We therefore generate our own session index. - */ - $sessionIndex = SimpleSAML\Utils\Random::generateID(); - } - - $store = \SimpleSAML\Store::getInstance(); - if ($store === false) { - // We don't have a datastore. - return; - } - - // serialize and anonymize the NameID + assert(is_string($authId)); + assert(is_string($sessionIndex) || $sessionIndex === null); + assert(is_int($expire)); + + if ($sessionIndex === null) { + /* This IdP apparently did not include a SessionIndex, and thus probably does not + * support SLO. We still want to add the session to the data store just in case + * it supports SLO, but we don't want an LogoutRequest with a specific + * SessionIndex to match this session. We therefore generate our own session index. + */ + $sessionIndex = SimpleSAML\Utils\Random::generateID(); + } + + $store = \SimpleSAML\Store::getInstance(); + if ($store === false) { + // We don't have a datastore. + return; + } + + // serialize and anonymize the NameID // TODO: remove this conditional statement - if (is_array($nameId)) { - $nameId = \SAML2\XML\saml\NameID::fromArray($nameId); - } - $strNameId = serialize($nameId); - $strNameId = sha1($strNameId); - - /* Normalize SessionIndex. */ - if (strlen($sessionIndex) > 50) { - $sessionIndex = sha1($sessionIndex); - } - - $session = SimpleSAML_Session::getSessionFromRequest(); - $sessionId = $session->getSessionId(); - - if ($store instanceof \SimpleSAML\Store\SQL) { - self::addSessionSQL($store, $authId, $strNameId, $sessionIndex, $expire, $sessionId); - } else { - $store->set('saml.LogoutStore', $strNameId . ':' . $sessionIndex, $sessionId, $expire); - } - } - - - /** - * Log out of the given sessions. - * - * @param string $authId The authsource ID. - * @param \SAML2\XML\saml\NameID $nameId The NameID of the user. - * @param array $sessionIndexes The SessionIndexes we should log out of. Logs out of all if this is empty. - * @returns int|FALSE Number of sessions logged out, or FALSE if not supported. - */ - public static function logoutSessions($authId, $nameId, array $sessionIndexes) + if (is_array($nameId)) { + $nameId = \SAML2\XML\saml\NameID::fromArray($nameId); + } + $strNameId = serialize($nameId); + $strNameId = sha1($strNameId); + + /* Normalize SessionIndex. */ + if (strlen($sessionIndex) > 50) { + $sessionIndex = sha1($sessionIndex); + } + + $session = SimpleSAML_Session::getSessionFromRequest(); + $sessionId = $session->getSessionId(); + + if ($store instanceof \SimpleSAML\Store\SQL) { + self::addSessionSQL($store, $authId, $strNameId, $sessionIndex, $expire, $sessionId); + } else { + $store->set('saml.LogoutStore', $strNameId . ':' . $sessionIndex, $sessionId, $expire); + } + } + + + /** + * Log out of the given sessions. + * + * @param string $authId The authsource ID. + * @param \SAML2\XML\saml\NameID $nameId The NameID of the user. + * @param array $sessionIndexes The SessionIndexes we should log out of. Logs out of all if this is empty. + * @returns int|false Number of sessions logged out, or FALSE if not supported. + */ + public static function logoutSessions($authId, $nameId, array $sessionIndexes) { - assert(is_string($authId)); - - $store = \SimpleSAML\Store::getInstance(); - if ($store === false) { - /* We don't have a datastore. */ - return false; - } - - // serialize and anonymize the NameID - // TODO: remove this conditional statement - if (is_array($nameId)) { - $nameId = \SAML2\XML\saml\NameID::fromArray($nameId); - } - $strNameId = serialize($nameId); - $strNameId = sha1($strNameId); - - /* Normalize SessionIndexes. */ - foreach ($sessionIndexes as &$sessionIndex) { - assert(is_string($sessionIndex)); - if (strlen($sessionIndex) > 50) { - $sessionIndex = sha1($sessionIndex); - } - } - unset($sessionIndex); // Remove reference - - if ($store instanceof \SimpleSAML\Store\SQL) { - $sessions = self::getSessionsSQL($store, $authId, $strNameId); - } elseif (empty($sessionIndexes)) { - /* We cannot fetch all sessions without a SQL store. */ - return false; - } else { - /** @var \SimpleSAML\Store $sessions At this point the store cannot be false */ - $sessions = self::getSessionsStore($store, $authId, $strNameId, $sessionIndexes); - - } - - if (empty($sessionIndexes)) { - $sessionIndexes = array_keys($sessions); - } - - $numLoggedOut = 0; - foreach ($sessionIndexes as $sessionIndex) { - if (!isset($sessions[$sessionIndex])) { - SimpleSAML\Logger::info('saml.LogoutStore: Logout requested for unknown SessionIndex.'); - continue; - } - - $sessionId = $sessions[$sessionIndex]; - - $session = SimpleSAML_Session::getSession($sessionId); - if ($session === null) { - SimpleSAML\Logger::info('saml.LogoutStore: Skipping logout of missing session.'); - continue; - } - - if (!$session->isValid($authId)) { - SimpleSAML\Logger::info('saml.LogoutStore: Skipping logout of session because it isn\'t authenticated.'); - continue; - } - - SimpleSAML\Logger::info('saml.LogoutStore: Logging out of session with trackId [' . $session->getTrackID() . '].'); - $session->doLogout($authId); - $numLoggedOut += 1; - } - - return $numLoggedOut; - } + assert(is_string($authId)); + + $store = \SimpleSAML\Store::getInstance(); + if ($store === false) { + /* We don't have a datastore. */ + return false; + } + + // serialize and anonymize the NameID + // TODO: remove this conditional statement + if (is_array($nameId)) { + $nameId = \SAML2\XML\saml\NameID::fromArray($nameId); + } + $strNameId = serialize($nameId); + $strNameId = sha1($strNameId); + + /* Normalize SessionIndexes. */ + foreach ($sessionIndexes as &$sessionIndex) { + assert(is_string($sessionIndex)); + if (strlen($sessionIndex) > 50) { + $sessionIndex = sha1($sessionIndex); + } + } + unset($sessionIndex); // Remove reference + + if ($store instanceof \SimpleSAML\Store\SQL) { + $sessions = self::getSessionsSQL($store, $authId, $strNameId); + } elseif (empty($sessionIndexes)) { + /* We cannot fetch all sessions without a SQL store. */ + return false; + } else { + /** @var \SimpleSAML\Store $sessions At this point the store cannot be false */ + $sessions = self::getSessionsStore($store, $authId, $strNameId, $sessionIndexes); + + } + + if (empty($sessionIndexes)) { + $sessionIndexes = array_keys($sessions); + } + + $numLoggedOut = 0; + foreach ($sessionIndexes as $sessionIndex) { + if (!isset($sessions[$sessionIndex])) { + SimpleSAML\Logger::info('saml.LogoutStore: Logout requested for unknown SessionIndex.'); + continue; + } + + $sessionId = $sessions[$sessionIndex]; + + $session = SimpleSAML_Session::getSession($sessionId); + if ($session === null) { + SimpleSAML\Logger::info('saml.LogoutStore: Skipping logout of missing session.'); + continue; + } + + if (!$session->isValid($authId)) { + SimpleSAML\Logger::info('saml.LogoutStore: Skipping logout of session because it isn\'t authenticated.'); + continue; + } + + SimpleSAML\Logger::info('saml.LogoutStore: Logging out of session with trackId [' . $session->getTrackID() . '].'); + $session->doLogout($authId); + $numLoggedOut += 1; + } + + return $numLoggedOut; + } } diff --git a/modules/saml/www/idp/certs.php b/modules/saml/www/idp/certs.php index a289d96a2cb473ddb5e8d6e3bd40469db50f5d19..adbfcc80dacfcb28703fc1a17cde41e3f4db76d2 100644 --- a/modules/saml/www/idp/certs.php +++ b/modules/saml/www/idp/certs.php @@ -4,8 +4,9 @@ $config = SimpleSAML_Configuration::getInstance(); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); -if (!$config->getBoolean('enable.saml20-idp', false)) - throw new SimpleSAML_Error_Error('NOACCESS'); +if (!$config->getBoolean('enable.saml20-idp', false)) { + throw new SimpleSAML_Error_Error('NOACCESS'); +} // Check if valid local session exists.. if ($config->getBoolean('admin.protectmetadata', false)) { @@ -16,17 +17,17 @@ $idpentityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted'); switch($_SERVER['PATH_INFO']) { - case '/new_idp.crt': - $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, FALSE, 'new_'); - break; - case '/idp.crt': - $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, TRUE); - break; - case '/https.crt': - $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, TRUE, 'https.'); - break; - default: - throw new SimpleSAML_Error_NotFound('Unknown certificate.'); + case '/new_idp.crt': + $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, false, 'new_'); + break; + case '/idp.crt': + $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, true); + break; + case '/https.crt': + $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, true, 'https.'); + break; + default: + throw new SimpleSAML_Error_NotFound('Unknown certificate.'); } header('Content-Disposition: attachment; filename='.substr($_SERVER['PATH_INFO'], 1)); diff --git a/modules/saml/www/sp/discoresp.php b/modules/saml/www/sp/discoresp.php index db9bda9d5b92fc42f40831fda84c07459b0c1f47..cc55607b1e5b5318d0a8a3c6d4771d69c4549e43 100644 --- a/modules/saml/www/sp/discoresp.php +++ b/modules/saml/www/sp/discoresp.php @@ -5,11 +5,11 @@ */ if (!array_key_exists('AuthID', $_REQUEST)) { - throw new SimpleSAML_Error_BadRequest('Missing AuthID to discovery service response handler'); + throw new SimpleSAML_Error_BadRequest('Missing AuthID to discovery service response handler'); } if (!array_key_exists('idpentityid', $_REQUEST)) { - throw new SimpleSAML_Error_BadRequest('Missing idpentityid to discovery service response handler'); + throw new SimpleSAML_Error_BadRequest('Missing idpentityid to discovery service response handler'); } $state = SimpleSAML_Auth_State::loadState($_REQUEST['AuthID'], 'saml:sp:sso'); @@ -18,11 +18,11 @@ assert(array_key_exists('saml:sp:AuthId', $state)); $sourceId = $state['saml:sp:AuthId']; $source = SimpleSAML_Auth_Source::getById($sourceId); -if ($source === NULL) { - throw new Exception('Could not find authentication source with id ' . $sourceId); +if ($source === null) { + throw new Exception('Could not find authentication source with id ' . $sourceId); } if (!($source instanceof sspmod_saml_Auth_Source_SP)) { - throw new SimpleSAML_Error_Exception('Source type changed?'); + throw new SimpleSAML_Error_Exception('Source type changed?'); } $source->startSSO($_REQUEST['idpentityid'], $state); diff --git a/modules/saml/www/sp/saml1-acs.php b/modules/saml/www/sp/saml1-acs.php index fa1a0efb4e47803738899545578d87ec1dcba31f..830eb53cc711ea7140cfe889620ef938184f95b3 100644 --- a/modules/saml/www/sp/saml1-acs.php +++ b/modules/saml/www/sp/saml1-acs.php @@ -3,11 +3,11 @@ use SimpleSAML\Bindings\Shib13\Artifact; if (!array_key_exists('SAMLResponse', $_REQUEST) && !array_key_exists('SAMLart', $_REQUEST)) { - throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart parameter.'); + throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart parameter.'); } if (!array_key_exists('TARGET', $_REQUEST)) { - throw new SimpleSAML_Error_BadRequest('Missing TARGET parameter.'); + throw new SimpleSAML_Error_BadRequest('Missing TARGET parameter.'); } if (!array_key_exists('PATH_INFO', $_SERVER)) { @@ -16,8 +16,8 @@ if (!array_key_exists('PATH_INFO', $_SERVER)) { $sourceId = $_SERVER['PATH_INFO']; $end = strpos($sourceId, '/', 1); -if ($end === FALSE) { - $end = strlen($sourceId); +if ($end === false) { + $end = strlen($sourceId); } $sourceId = substr($sourceId, 1, $end - 1); @@ -28,41 +28,41 @@ SimpleSAML\Logger::debug('Received SAML1 response'); $target = (string)$_REQUEST['TARGET']; if (preg_match('@^https?://@i', $target)) { - // Unsolicited response - $state = array( - 'saml:sp:isUnsolicited' => TRUE, - 'saml:sp:AuthId' => $sourceId, - 'saml:sp:RelayState' => \SimpleSAML\Utils\HTTP::checkURLAllowed($target), - ); + // Unsolicited response + $state = array( + 'saml:sp:isUnsolicited' => true, + 'saml:sp:AuthId' => $sourceId, + 'saml:sp:RelayState' => \SimpleSAML\Utils\HTTP::checkURLAllowed($target), + ); } else { - $state = SimpleSAML_Auth_State::loadState($_REQUEST['TARGET'], 'saml:sp:sso'); + $state = SimpleSAML_Auth_State::loadState($_REQUEST['TARGET'], 'saml:sp:sso'); - // Check that the authentication source is correct. - assert(array_key_exists('saml:sp:AuthId', $state)); - if ($state['saml:sp:AuthId'] !== $sourceId) { - throw new SimpleSAML_Error_Exception('The authentication source id in the URL does not match the authentication source which sent the request.'); - } + // Check that the authentication source is correct. + assert(array_key_exists('saml:sp:AuthId', $state)); + if ($state['saml:sp:AuthId'] !== $sourceId) { + throw new SimpleSAML_Error_Exception('The authentication source id in the URL does not match the authentication source which sent the request.'); + } - assert(isset($state['saml:idp'])); + assert(isset($state['saml:idp'])); } $spMetadata = $source->getMetadata(); if (array_key_exists('SAMLart', $_REQUEST)) { - if (!isset($state['saml:idp'])) { - /* Unsolicited response. */ - throw new SimpleSAML_Error_Exception('IdP initiated authentication not supported with the SAML 1.1 SAMLart protocol.'); - } - $idpMetadata = $source->getIdPMetadata($state['saml:idp']); - - $responseXML = Artifact::receive($spMetadata, $idpMetadata); - $isValidated = TRUE; /* Artifact binding validated with ssl certificate. */ + if (!isset($state['saml:idp'])) { + /* Unsolicited response. */ + throw new SimpleSAML_Error_Exception('IdP initiated authentication not supported with the SAML 1.1 SAMLart protocol.'); + } + $idpMetadata = $source->getIdPMetadata($state['saml:idp']); + + $responseXML = Artifact::receive($spMetadata, $idpMetadata); + $isValidated = true; /* Artifact binding validated with ssl certificate. */ } elseif (array_key_exists('SAMLResponse', $_REQUEST)) { - $responseXML = $_REQUEST['SAMLResponse']; - $responseXML = base64_decode($responseXML); - $isValidated = FALSE; /* Must check signature on response. */ + $responseXML = $_REQUEST['SAMLResponse']; + $responseXML = base64_decode($responseXML); + $isValidated = false; /* Must check signature on response. */ } else { - assert(false); + assert(false); } $response = new \SimpleSAML\XML\Shib13\AuthnResponse(); @@ -75,12 +75,12 @@ $responseIssuer = $response->getIssuer(); $attributes = $response->getAttributes(); if (isset($state['saml:idp']) && $responseIssuer !== $state['saml:idp']) { - throw new SimpleSAML_Error_Exception('The issuer of the response wasn\'t the destination of the request.'); + throw new SimpleSAML_Error_Exception('The issuer of the response wasn\'t the destination of the request.'); } $logoutState = array( - 'saml:logout:Type' => 'saml1' - ); + 'saml:logout:Type' => 'saml1' + ); $state['LogoutState'] = $logoutState; $state['saml:sp:NameID'] = $response->getNameID(); diff --git a/modules/saml/www/sp/saml2-logout.php b/modules/saml/www/sp/saml2-logout.php index 6fa5a0081791bc2d3588095822fc4c9a8eecfe8c..88826f6555089d715c70342ec986b4272c2d0fb6 100644 --- a/modules/saml/www/sp/saml2-logout.php +++ b/modules/saml/www/sp/saml2-logout.php @@ -7,17 +7,17 @@ */ if (!array_key_exists('PATH_INFO', $_SERVER)) { - throw new SimpleSAML_Error_BadRequest('Missing authentication source ID in logout URL'); + throw new SimpleSAML_Error_BadRequest('Missing authentication source ID in logout URL'); } $sourceId = substr($_SERVER['PATH_INFO'], 1); $source = SimpleSAML_Auth_Source::getById($sourceId); -if ($source === NULL) { - throw new Exception('Could not find authentication source with id ' . $sourceId); +if ($source === null) { + throw new Exception('Could not find authentication source with id ' . $sourceId); } if (!($source instanceof sspmod_saml_Auth_Source_SP)) { - throw new SimpleSAML_Error_Exception('Source type changed?'); + throw new SimpleSAML_Error_Exception('Source type changed?'); } try { @@ -34,9 +34,9 @@ try { $message = $binding->receive(); $idpEntityId = $message->getIssuer(); -if ($idpEntityId === 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.'); +if ($idpEntityId === 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.'); } $spEntityId = $source->getEntityId(); @@ -48,93 +48,93 @@ $spMetadata = $source->getMetadata(); sspmod_saml_Message::validateMessage($idpMetadata, $spMetadata, $message); $destination = $message->getDestination(); -if ($destination !== NULL && $destination !== \SimpleSAML\Utils\HTTP::getSelfURLNoQuery()) { - throw new SimpleSAML_Error_Exception('Destination in logout message is wrong.'); +if ($destination !== null && $destination !== \SimpleSAML\Utils\HTTP::getSelfURLNoQuery()) { + throw new SimpleSAML_Error_Exception('Destination in logout message is wrong.'); } if ($message instanceof \SAML2\LogoutResponse) { - $relayState = $message->getRelayState(); - if ($relayState === NULL) { - // Somehow, our RelayState has been lost. - throw new SimpleSAML_Error_BadRequest('Missing RelayState in logout response.'); - } + $relayState = $message->getRelayState(); + if ($relayState === null) { + // Somehow, our RelayState has been lost. + throw new SimpleSAML_Error_BadRequest('Missing RelayState in logout response.'); + } - if (!$message->isSuccess()) { - SimpleSAML\Logger::warning('Unsuccessful logout. Status was: ' . sspmod_saml_Message::getResponseError($message)); - } + if (!$message->isSuccess()) { + SimpleSAML\Logger::warning('Unsuccessful logout. Status was: ' . sspmod_saml_Message::getResponseError($message)); + } - $state = SimpleSAML_Auth_State::loadState($relayState, 'saml:slosent'); - $state['saml:sp:LogoutStatus'] = $message->getStatus(); - SimpleSAML_Auth_Source::completeLogout($state); + $state = SimpleSAML_Auth_State::loadState($relayState, 'saml:slosent'); + $state['saml:sp:LogoutStatus'] = $message->getStatus(); + SimpleSAML_Auth_Source::completeLogout($state); } elseif ($message instanceof \SAML2\LogoutRequest) { - SimpleSAML\Logger::debug('module/saml2/sp/logout: Request from ' . $idpEntityId); - SimpleSAML\Logger::stats('saml20-idp-SLO idpinit ' . $spEntityId . ' ' . $idpEntityId); - - if ($message->isNameIdEncrypted()) { - try { - $keys = sspmod_saml_Message::getDecryptionKeys($idpMetadata, $spMetadata); - } catch (Exception $e) { - throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); - } - - $blacklist = sspmod_saml_Message::getBlacklistedAlgorithms($idpMetadata, $spMetadata); - - $lastException = NULL; - foreach ($keys as $i => $key) { - try { - $message->decryptNameId($key, $blacklist); - SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.'); - $lastException = NULL; - break; - } catch (Exception $e) { - SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); - $lastException = $e; - } - } - if ($lastException !== NULL) { - throw $lastException; - } - } - - $nameId = $message->getNameId(); - $sessionIndexes = $message->getSessionIndexes(); - - $numLoggedOut = sspmod_saml_SP_LogoutStore::logoutSessions($sourceId, $nameId, $sessionIndexes); - if ($numLoggedOut === FALSE) { - /* This type of logout was unsupported. Use the old method. */ - $source->handleLogout($idpEntityId); - $numLoggedOut = count($sessionIndexes); - } - - /* Create an send response. */ - $lr = sspmod_saml_Message::buildLogoutResponse($spMetadata, $idpMetadata); - $lr->setRelayState($message->getRelayState()); - $lr->setInResponseTo($message->getId()); - - if ($numLoggedOut < count($sessionIndexes)) { - SimpleSAML\Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.'); - } - - $dst = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array( - \SAML2\Constants::BINDING_HTTP_REDIRECT, - \SAML2\Constants::BINDING_HTTP_POST) - ); - - if (!$binding instanceof \SAML2\SOAP) { - $binding = \SAML2\Binding::getBinding($dst['Binding']); - if (isset($dst['ResponseLocation'])) { - $dst = $dst['ResponseLocation']; - } else { - $dst = $dst['Location']; - } - $binding->setDestination($dst); - } - $lr->setDestination($dst); - - $binding->send($lr); + SimpleSAML\Logger::debug('module/saml2/sp/logout: Request from ' . $idpEntityId); + SimpleSAML\Logger::stats('saml20-idp-SLO idpinit ' . $spEntityId . ' ' . $idpEntityId); + + if ($message->isNameIdEncrypted()) { + try { + $keys = sspmod_saml_Message::getDecryptionKeys($idpMetadata, $spMetadata); + } catch (Exception $e) { + throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); + } + + $blacklist = sspmod_saml_Message::getBlacklistedAlgorithms($idpMetadata, $spMetadata); + + $lastException = null; + foreach ($keys as $i => $key) { + try { + $message->decryptNameId($key, $blacklist); + SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.'); + $lastException = null; + break; + } catch (Exception $e) { + SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); + $lastException = $e; + } + } + if ($lastException !== null) { + throw $lastException; + } + } + + $nameId = $message->getNameId(); + $sessionIndexes = $message->getSessionIndexes(); + + $numLoggedOut = sspmod_saml_SP_LogoutStore::logoutSessions($sourceId, $nameId, $sessionIndexes); + if ($numLoggedOut === false) { + /* This type of logout was unsupported. Use the old method. */ + $source->handleLogout($idpEntityId); + $numLoggedOut = count($sessionIndexes); + } + + /* Create and send response. */ + $lr = sspmod_saml_Message::buildLogoutResponse($spMetadata, $idpMetadata); + $lr->setRelayState($message->getRelayState()); + $lr->setInResponseTo($message->getId()); + + if ($numLoggedOut < count($sessionIndexes)) { + SimpleSAML\Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.'); + } + + $dst = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array( + \SAML2\Constants::BINDING_HTTP_REDIRECT, + \SAML2\Constants::BINDING_HTTP_POST) + ); + + if (!$binding instanceof \SAML2\SOAP) { + $binding = \SAML2\Binding::getBinding($dst['Binding']); + if (isset($dst['ResponseLocation'])) { + $dst = $dst['ResponseLocation']; + } else { + $dst = $dst['Location']; + } + $binding->setDestination($dst); + } + $lr->setDestination($dst); + + $binding->send($lr); } else { - throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message)); + throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message)); }