diff --git a/docs/simplesamlphp-authproc.txt b/docs/simplesamlphp-authproc.txt index e5b6a42141c1aa27b65eb4906d10b4194237be10..145bd996c4d507ad0a8faeebfec79429efbf0f19 100644 --- a/docs/simplesamlphp-authproc.txt +++ b/docs/simplesamlphp-authproc.txt @@ -142,6 +142,9 @@ The following filters are included in the simpleSAMLphp distribution: - [`core:TargetedID`](./core:authproc_targetedid): Generate the `eduPersonTargetedID` attribute. - [`core:WarnShortSSOInterval`](./core:authproc_warnshortssointerval): Give a warning if the user logs into the same SP twice within a few seconds. - [`preprodwarning:Warning`](./preprodwarning:warning): Warn the user about accessing a test IdP. +- [`saml:AttributeNameID`](./saml:nameid): Generate custon NameID with the value of an attribute. +- [`saml:PersistentNameID`](./saml:nameid): Generate persistent NameID from an attribute. +- [`saml:TransientNameID`](./saml:nameid): Generate transient NameID. diff --git a/modules/saml/docs/nameid.txt b/modules/saml/docs/nameid.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1d0da4f89e9949900858a53bc922602ae790909 --- /dev/null +++ b/modules/saml/docs/nameid.txt @@ -0,0 +1,81 @@ +NameID generation filters +========================= + +This document describes the NameID generation filters in the saml module. + + +Common options +-------------- + +`NameQualifier` +: The NameQualifier attribute for the generated NameID. + This can be a string that is used as the value directly. + It can also be `TRUE`, in which case we use the IdP entity ID as the NameQualifier. + If it is `FALSE`, no NameQualifier will be included. + +: The default is `FALSE`, which means that we will not include a NameQualifier by default. + +`SPNameQualifier` +: The SPNameQualifier attribute for the generated NameID. + This can be a string that is used as the value directly. + It can also be `TRUE`, in which case we use the SP entity ID as the SPNameQualifier. + If it is `FALSE`, no SPNameQualifier will be included. + +: The default is `TRUE`, which means that we will use the SP entity ID. + + +`saml:AttributeNameID` +---------------------- + +Uses the value of an attribute to generate a NameID. + +### Options + +`attribute` +: The name of the attribute we should use as the unique user ID. + +`Format` +: The `Format` attribute of the generated NameID. + + + +`saml:PersistentNameID` +----------------------- + +Generates a persistent NameID with the format `urn:oasis:names:tc:SAML:2.0:nameid-format:persistent`. +The filter will take the user ID from the attribute described in the `attribute` option, and hash it with the `secretsalt` from `config.php`, and the SP and IdP entity ID. +The resulting hash is sent as the persistent NameID. + +### Options + +`attribute` +: The name of the attribute we should use as the unique user ID. + + +`saml:TransientNameID` +---------------------- + +Generates a transient NameID with the format `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. + +No extra options are available for this filter. + + +Example +------- + +This example makes three NameIDs available: + + 'authproc' => array( + 1 => array( + 'class' => 'saml:TransientNameID', + ), + 2 => array( + 'class' => 'saml:PersistentNameID', + 'attribute' => 'eduPersonPrincipalName', + ), + 3 => array( + 'class' => 'saml:AttributeNameID', + 'attribute' => 'eduPersonPrincipalName', + 'Format' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', + ), + ), diff --git a/modules/saml/lib/Auth/Process/AttributeNameID.php b/modules/saml/lib/Auth/Process/AttributeNameID.php new file mode 100644 index 0000000000000000000000000000000000000000..22fa41858f832ecaa6f680d98951a0111b3197d0 --- /dev/null +++ b/modules/saml/lib/Auth/Process/AttributeNameID.php @@ -0,0 +1,60 @@ +<?php + +/** + * Authproc filter to create a NameID from an attribute. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_saml_Auth_Process_AttributeNameID extends sspmod_saml_BaseNameIDGenerator { + + /** + * The attribute we should use as the NameID. + * + * @var string + */ + private $attribute; + + + /** + * 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['Format'])) { + throw new SimpleSAML_Error_Exception('AttributeNameID: Missing required option \'Format\'.'); + } + $this->format = (string)$config['Format']; + + if (!isset($config['attribute'])) { + throw new SimpleSAML_Error_Exception('AttributeNameID: Missing required option \'attribute\'.'); + } + $this->attribute = (string)$config['attribute']; + } + + + /** + * Get the NameID value. + * + * @return string|NULL The NameID value. + */ + protected function getValue(array &$state) { + + if (!isset($state['Attributes'][$this->attribute]) || count($state['Attributes'][$this->attribute]) === 0) { + SimpleSAML_Logger::warning('Missing attribute ' . var_export($this->attribute, TRUE) . ' on user - not generating attribute NameID.'); + return NULL; + } + if (count($state['Attributes'][$this->attribute]) > 1) { + SimpleSAML_Logger::warning('More than one value in attribute ' . var_export($this->attribute, TRUE) . ' on user - not generating attribute NameID.'); + } + $value = array_values($state['Attributes'][$this->attribute]); /* Just in case the first index is no longer 0. */ + $value = $value[0]; + return $value; + } + +} diff --git a/modules/saml/lib/Auth/Process/PersistentNameID.php b/modules/saml/lib/Auth/Process/PersistentNameID.php new file mode 100644 index 0000000000000000000000000000000000000000..aa0a243bce8912faed92466e90477299370c72f3 --- /dev/null +++ b/modules/saml/lib/Auth/Process/PersistentNameID.php @@ -0,0 +1,78 @@ +<?php + +/** + * Authproc filter to generate a persistent NameID. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_saml_Auth_Process_PersistentNameID extends sspmod_saml_BaseNameIDGenerator { + + /** + * Which attribute contains the unique identifier of the user. + * + * @var string + */ + private $attribute; + + + /** + * 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)'); + + $this->format = SAML2_Const::NAMEID_PERSISTENT; + + if (!isset($config['attribute'])) { + throw new SimpleSAML_Error_Exception('PersistentNameID: Missing required option \'attribute\'.'); + } + $this->attribute = $config['attribute']; + } + + + /** + * Get the NameID value. + * + * @return string|NULL The NameID value. + */ + protected function getValue(array &$state) { + + if (!isset($state['SPMetadata']['entityid'])) { + SimpleSAML_Logger::warning('No SP entity ID - not generating persistent NameID.'); + return NULL; + } + $spEntityId = $state['SPMetadata']['entityid']; + + if (!isset($state['IdPMetadata']['entityid'])) { + SimpleSAML_Logger::warning('No IdP entity ID - not generating persistent NameID.'); + return NULL; + } + $idpEntityId = $state['IdPMetadata']['entityid']; + + if (!isset($state['Attributes'][$this->attribute]) || count($state['Attributes'][$this->attribute]) === 0) { + SimpleSAML_Logger::warning('Missing attribute ' . var_export($this->attribute, TRUE) . ' on user - not generating persistent NameID.'); + return NULL; + } + if (count($state['Attributes'][$this->attribute]) > 1) { + SimpleSAML_Logger::warning('More than one value in attribute ' . var_export($this->attribute, TRUE) . ' on user - not generating persistent NameID.'); + } + $uid = array_values($state['Attributes'][$this->attribute]); /* Just in case the first index is no longer 0. */ + $uid = $uid[0]; + + $secretSalt = SimpleSAML_Utilities::getSecretSalt(); + + $uidData = 'saml:PersistentNameID:' . $secretSalt; + $uidData .= strlen($idpEntityId) . ':' . $idpEntityId; + $uidData .= strlen($spEntityId) . ':' . $spEntityId; + $uidData .= strlen($uid) . ':' . $uid; + $uidData .= $secretSalt; + + return sha1($uidData); + } + +} diff --git a/modules/saml/lib/Auth/Process/TransientNameID.php b/modules/saml/lib/Auth/Process/TransientNameID.php new file mode 100644 index 0000000000000000000000000000000000000000..44e4e20fa6bf67e5f80484fbd30feda63bf09bb1 --- /dev/null +++ b/modules/saml/lib/Auth/Process/TransientNameID.php @@ -0,0 +1,35 @@ +<?php + +/** + * Authproc filter to generate a transient NameID. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_saml_Auth_Process_TransientNameID extends sspmod_saml_BaseNameIDGenerator { + + /** + * 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)'); + + $this->format = SAML2_Const::NAMEID_TRANSIENT; + } + + + /** + * Get the NameID value. + * + * @return string|NULL The NameID value. + */ + protected function getValue(array &$state) { + + return SimpleSAML_Utilities::generateID(); + } + +} diff --git a/modules/saml/lib/BaseNameIDGenerator.php b/modules/saml/lib/BaseNameIDGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..177079a5de39e339320a3ff641832b7480b9a3da --- /dev/null +++ b/modules/saml/lib/BaseNameIDGenerator.php @@ -0,0 +1,116 @@ +<?php + +/** + * Base filter for generating NameID values. + * + * @package simpleSAMLphp + * @version $Id$ + */ +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 = array('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; + } + +}