diff options
Diffstat (limited to 'modules')
18 files changed, 2298 insertions, 2140 deletions
diff --git a/modules/authX509/lib/Auth/Process/ExpiryWarning.php b/modules/authX509/lib/Auth/Process/ExpiryWarning.php index a108d95..7a8841e 100644 --- a/modules/authX509/lib/Auth/Process/ExpiryWarning.php +++ b/modules/authX509/lib/Auth/Process/ExpiryWarning.php @@ -14,7 +14,8 @@ * @author Joost van Dijk, SURFnet. <Joost.vanDijk@surfnet.nl> * @package SimpleSAMLphp */ -class sspmod_authX509_Auth_Process_ExpiryWarning extends SimpleSAML_Auth_ProcessingFilter { +class sspmod_authX509_Auth_Process_ExpiryWarning extends SimpleSAML_Auth_ProcessingFilter +{ private $warndaysbefore = 30; private $renewurl = null; @@ -25,7 +26,8 @@ class sspmod_authX509_Auth_Process_ExpiryWarning extends SimpleSAML_Auth_Process * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. */ - public function __construct($config, $reserved) { + public function __construct($config, $reserved) + { parent::__construct($config, $reserved); assert('is_array($config)'); @@ -53,7 +55,8 @@ class sspmod_authX509_Auth_Process_ExpiryWarning extends SimpleSAML_Auth_Process * * @param array $state The state of the response. */ - public function process(&$state) { + public function process(&$state) + { assert('is_array($state)'); if (isset($state['isPassive']) && $state['isPassive'] === true) { diff --git a/modules/authX509/lib/Auth/Source/X509userCert.php b/modules/authX509/lib/Auth/Source/X509userCert.php index e9bf052..74729dd 100644 --- a/modules/authX509/lib/Auth/Source/X509userCert.php +++ b/modules/authX509/lib/Auth/Source/X509userCert.php @@ -7,7 +7,8 @@ * @author Emmanuel Dreyfus <manu@netbsd.org> * @package SimpleSAMLphp */ -class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { +class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source +{ /** * x509 attributes to use from the certificate @@ -37,17 +38,20 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * @param array $info Information about this authentication source. * @param array &$config Configuration for this authentication source. */ - public function __construct($info, &$config) { + public function __construct($info, &$config) + { assert('is_array($info)'); assert('is_array($config)'); - if (isset($config['authX509:x509attributes'])) + if (isset($config['authX509:x509attributes'])) { $this->x509attributes = $config['authX509:x509attributes']; + } - if (array_key_exists('authX509:ldapusercert', $config)) + if (array_key_exists('authX509:ldapusercert', $config)) { $this->ldapusercert = $config['authX509:ldapusercert']; + } parent::__construct($info, $config); @@ -63,7 +67,8 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * * @param array $pem_data PEM-encoded certificate */ - private function pem2der($pem_data) { + private function pem2der($pem_data) + { $begin = "CERTIFICATE-----"; $end = "-----END"; $pem_data = substr($pem_data, @@ -79,7 +84,8 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * * @param array $der_data DER-encoded certificate */ - private function der2pem($der_data) { + private function der2pem($der_data) + { $pem = chunk_split(base64_encode($der_data), 64, "\n"); $pem = "-----BEGIN CERTIFICATE-----\n".$pem. "-----END CERTIFICATE-----\n"; @@ -95,7 +101,8 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * * @param array &$state Information about the current authentication. */ - public function authFailed(&$state) { + public function authFailed(&$state) + { $config = SimpleSAML_Configuration::getInstance(); $t = new SimpleSAML_XHTML_Template($config, @@ -118,7 +125,8 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * * @param array &$state Information about the current authentication. */ - public function authenticate(&$state) { + public function authenticate(&$state) + { assert('is_array($state)'); $ldapcf = $this->ldapcf; @@ -232,7 +240,8 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source { * * @param array &$state Information about the current authentication. */ - public function authSuccesful(&$state) { + public function authSuccesful(&$state) + { SimpleSAML_Auth_Source::completeAuth($state); assert('false'); /* NOTREACHED */ diff --git a/modules/core/docs/authproc_attributerealm.md b/modules/core/docs/authproc_attributerealm.md index 77b0bb3..cf51177 100644 --- a/modules/core/docs/authproc_attributerealm.md +++ b/modules/core/docs/authproc_attributerealm.md @@ -1,6 +1,9 @@ `core:AttributeRealm` ===================== +*NOTE:* This filter has been deprecated and will be removed in a future release. Please use +`core:ScopeFromAttribute` instead. + This filter creates a new attribute with the realm of the user. The new attribute is names `realm` by default, but can be controlled by the `attributename` option. diff --git a/modules/core/lib/Auth/Process/AttributeRealm.php b/modules/core/lib/Auth/Process/AttributeRealm.php index a4755a0..9e50d78 100644 --- a/modules/core/lib/Auth/Process/AttributeRealm.php +++ b/modules/core/lib/Auth/Process/AttributeRealm.php @@ -6,49 +6,48 @@ * * @author Andreas Åkre Solberg, UNINETT AS. * @package SimpleSAMLphp + * @deprecated Use ScopeFromAttribute instead. */ class sspmod_core_Auth_Process_AttributeRealm extends SimpleSAML_Auth_ProcessingFilter { - private $attributename = 'realm'; - - /** - * Initialize this filter. - * - * @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 (array_key_exists('attributename', $config)) - $this->attributename = $config['attributename']; - - } - - - /** - * Apply filter to add or replace attributes. - * - * Add or replace existing attributes with the configured values. - * - * @param array &$request The current request - */ - public function process(&$request) { - assert('is_array($request)'); - assert('array_key_exists("Attributes", $request)'); - - $attributes =& $request['Attributes']; - - if (!array_key_exists('UserID', $request)) { - throw new Exception('core:AttributeRealm: Missing UserID for this user. Please' . - ' check the \'userid.attribute\' option in the metadata against the' . - ' attributes provided by the authentication source.'); - } - $userID = $request['UserID']; - $decomposed = explode('@', $userID); - if (count($decomposed) !== 2) return; - $request['Attributes'][$this->attributename] = array($decomposed[1]); - } - + private $attributename = 'realm'; + + /** + * Initialize this filter. + * + * @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 (array_key_exists('attributename', $config)) + $this->attributename = $config['attributename']; + + } + + /** + * Apply filter to add or replace attributes. + * + * Add or replace existing attributes with the configured values. + * + * @param array &$request The current request + */ + public function process(&$request) { + assert('is_array($request)'); + assert('array_key_exists("Attributes", $request)'); + + $attributes =& $request['Attributes']; + + if (!array_key_exists('UserID', $request)) { + throw new Exception('core:AttributeRealm: Missing UserID for this user. Please' . + ' check the \'userid.attribute\' option in the metadata against the' . + ' attributes provided by the authentication source.'); + } + $userID = $request['UserID']; + $decomposed = explode('@', $userID); + if (count($decomposed) !== 2) return; + $request['Attributes'][$this->attributename] = array($decomposed[1]); + } } diff --git a/modules/ldap/docs/ldap.md b/modules/ldap/docs/ldap.md index cbc9e6f..098e404 100644 --- a/modules/ldap/docs/ldap.md +++ b/modules/ldap/docs/ldap.md @@ -462,13 +462,15 @@ a listing of all configuration options and their details. * that most products have a special query to recursively search * group membership. * - * Note: Only ActiveDirectory is currently supported. + * Note: Only ActiveDirectory is currently supported + * (OpenLDAP is implemented but not supported, see example below). * * Default: '' * Required: No */ 'ldap.product' => '', 'ldap.product' => 'ActiveDirectory', + 'ldap.product' => 'OpenLDAP', /** @@ -559,3 +561,14 @@ required, see the config info above for details. 'ldap.basedn' => 'DC=example,DC=org' ) +Example for unsupported OpenLDAP usage. +Intention is to filter in 'ou=groups,dc=example,dc=com' for +'(memberUid = <UID>)' and take only the attributes 'cn' (=name of the group). + + 50 => array( + 'class' => 'ldap:AttributeAddUsersGroups', + 'ldap.product' => 'OpenLDAP', + 'ldap.basedn' => 'ou=groups,dc=example,dc=org', + 'attribute.member' => 'cn', + 'attribute.memberof' => 'memberUid', + ), diff --git a/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php b/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php index 38c85c0..b1c5910 100644 --- a/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php +++ b/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php @@ -29,7 +29,8 @@ * @author Ryan Panning * @package SimpleSAMLphp */ -class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Process_BaseFilter { +class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Process_BaseFilter +{ /** * LDAP attribute to add to the request attributes @@ -60,8 +61,8 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. */ - public function __construct($config, $reserved) { - + public function __construct($config, $reserved) + { /* * For backwards compatibility, check for old config names * @TODO Remove after 2.0 @@ -119,7 +120,7 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro $new_attribute = $this->config->getString('attribute.new', ''); $this->search_attributes[$new_attribute] = $this->config->getString('search.attribute'); } - $this->search_filter = $this->config->getString('search.filter'); + $this->search_filter = $this->config->getString('search.filter'); // get the attribute policy $this->attr_policy = $this->config->getString('attribute.policy', 'merge'); @@ -131,7 +132,8 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro * * @param array &$request The current request */ - public function process(&$request) { + public function process(&$request) + { assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); @@ -153,7 +155,7 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro // merge the attributes into the ldap_search_filter $filter = str_replace($arrSearch, $arrReplace, $this->search_filter); - if (strpos($filter, '%') !== FALSE) { + if (strpos($filter, '%') !== false) { SimpleSAML\Logger::info('AttributeAddFromLDAP: There are non-existing attributes in the search filter. ('. $this->search_filter.')'); return; @@ -168,7 +170,7 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro // search for matching entries try { $entries = $this->getLdap()->searchformultiple($this->base_dn, $filter, - array_values($this->search_attributes), TRUE, FALSE); + array_values($this->search_attributes), true, false); } catch (Exception $e) { return; // silent fail, error is still logged by LDAP search } diff --git a/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php b/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php index 6364efe..ada4932 100644 --- a/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php +++ b/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php @@ -8,287 +8,312 @@ * @author Ryan Panning <panman@traileyes.com> * @package SimpleSAMLphp */ -class sspmod_ldap_Auth_Process_AttributeAddUsersGroups extends sspmod_ldap_Auth_Process_BaseFilter { - - - /** - * This is run when the filter is processed by SimpleSAML. - * It will attempt to find the current users groups using - * the best method possible for the LDAP product. The groups - * are then added to the request attributes. - * - * @throws SimpleSAML_Error_Exception - * @param $request - */ - public function process(&$request) { - assert('is_array($request)'); - assert('array_key_exists("Attributes", $request)'); - - // Log the process - SimpleSAML\Logger::debug( - $this->title . 'Attempting to get the users groups...' - ); - - // Reference the attributes, just to make the names shorter - $attributes =& $request['Attributes']; - $map =& $this->attribute_map; - - // Get the users groups from LDAP - $groups = $this->getGroups($attributes); - - // Make the array if it is not set already - if (!isset($attributes[$map['groups']])) { - $attributes[$map['groups']] = array(); - } - - // Must be an array, else cannot merge groups - if (!is_array($attributes[$map['groups']])) { - throw new SimpleSAML_Error_Exception( - $this->title . 'The group attribute [' . $map['groups'] . - '] is not an array of group DNs. ' . $this->var_export($attributes[$map['groups']]) - ); - } - - // Add the users group(s) - $group_attribute =& $attributes[$map['groups']]; - $group_attribute = array_merge($group_attribute, $groups); - $group_attribute = array_unique($group_attribute); - - // All done - SimpleSAML\Logger::debug( - $this->title . 'Added users groups to the group attribute [' . - $map['groups'] . ']: ' . implode('; ', $groups) - ); - } - - - /** - * This section of code was broken out because the child - * filter AuthorizeByGroup can use this method as well. - * Based on the LDAP product, it will do an optimized search - * using the required attribute values from the user to - * get their group membership, recursively. - * - * @throws SimpleSAML_Error_Exception - * @param array $attributes - * @return array - */ - protected function getGroups(array $attributes) { - - // Reference the map, just to make the name shorter - $map =& $this->attribute_map; - - // Log the request - SimpleSAML\Logger::debug( - $this->title . 'Checking for groups based on the best method for the LDAP product.' - ); - - // Based on the directory service, search LDAP for groups - // If any attributes are needed, prepare them before calling search method - switch ($this->product) { - - case 'ACTIVEDIRECTORY': - - // Log the AD specific search - SimpleSAML\Logger::debug( - $this->title . 'Searching LDAP using ActiveDirectory specific method.' - ); - - // Make sure the defined dn attribute exists - if (!isset($attributes[$map['dn']])) { - throw new SimpleSAML_Error_Exception( - $this->title . 'The DN attribute [' . $map['dn'] . - '] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes)) - ); - } - - // DN attribute must have a value - if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) { - throw new SimpleSAML_Error_Exception( - $this->title . 'The DN attribute [' . $map['dn'] . - '] does not have a [0] value defined. ' . $this->var_export($attributes[$map['dn']]) - ); - } - - // Pass to the AD specific search - $groups = $this->searchActiveDirectory($attributes[$map['dn']][0]); - break; - - default: - - // Log the general search - SimpleSAML\Logger::debug( - $this->title . 'Searching LDAP using the default search method.' - ); - - // Make sure the defined memberOf attribute exists - if (!isset($attributes[$map['memberof']])) { - throw new SimpleSAML_Error_Exception( - $this->title . 'The memberof attribute [' . $map['memberof'] . - '] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes)) - ); - } - - // MemberOf must be an array of group DN's - if (!is_array($attributes[$map['memberof']])) { - throw new SimpleSAML_Error_Exception( - $this->title . 'The memberof attribute [' . $map['memberof'] . - '] is not an array of group DNs. ' . $this->var_export($attributes[$map['memberof']]) - ); - } - - // Search for the users group membership, recursively - $groups = $this->search($attributes[$map['memberof']]); - } - - // All done - SimpleSAML\Logger::debug( - $this->title . 'User found to be a member of the groups:' . implode('; ', $groups) - ); - return $groups; - } - - - /** - * Looks for groups from the list of DN's passed. Also - * recursively searches groups for further membership. - * Avoids loops by only searching a DN once. Returns - * the list of groups found. - * - * @param array $memberof - * @return array - */ - protected function search($memberof) { - assert('is_array($memberof)'); - - // Used to determine what DN's have already been searched - static $searched = array(); - - // Init the groups variable - $groups = array(); - - // Shorten the variable name - $map =& $this->attribute_map; - - // Log the search - SimpleSAML\Logger::debug( - $this->title . 'Checking DNs for groups.' . - ' DNs: '. implode('; ', $memberof) . - ' Attributes: ' . $map['memberof'] . ', ' . $map['type'] . - ' Group Type: ' . $this->type_map['group'] - ); - - // Check each DN of the passed memberOf - foreach ($memberof as $dn) { - - // Avoid infinite loops, only need to check a DN once - if (isset($searched[$dn])) { - continue; - } - - // Track all DN's that are searched - // Use DN for key as well, isset() is faster than in_array() - $searched[$dn] = $dn; - - // Query LDAP for the attribute values for the DN - try { - $attributes = $this->getLdap()->getAttributes($dn, array($map['memberof'], $map['type'])); - } catch (SimpleSAML_Error_AuthSource $e) { - continue; // DN must not exist, just continue. Logged by the LDAP object - } - - // Only look for groups - if (!in_array($this->type_map['group'], $attributes[$map['type']])) { - continue; - } - - // Add to found groups array - $groups[] = $dn; - - // Recursively search "sub" groups - $groups = array_merge($groups, $this->search($attributes[$map['memberof']])); - } - - // Return only the unique group names - return array_unique($groups); - } - - - /** - * Searches LDAP using a ActiveDirectory specific filter, - * looking for group membership for the users DN. Returns - * the list of group DNs retrieved. - * - * @param string $dn - * @return array - */ - protected function searchActiveDirectory($dn) { - assert('is_string($dn) && $dn != ""'); - - // Shorten the variable name - $map =& $this->attribute_map; - - // Log the search - SimpleSAML\Logger::debug( - $this->title . 'Searching ActiveDirectory group membership.' . - ' DN: ' . $dn . - ' DN Attribute: ' . $map['dn'] . - ' Member Attribute: ' . $map['member'] . - ' Type Attribute: ' . $map['type'] . - ' Type Value: ' . $this->type_map['group'] . - ' Base: ' . implode('; ', $this->base_dn) - ); - - // AD connections should have this set - $this->getLdap()->setOption(LDAP_OPT_REFERRALS, 0); - - // Search AD with the specific recursive flag - try { - $entries = $this->getLdap()->searchformultiple( - $this->base_dn, - array($map['type'] => $this->type_map['group'], $map['member'] . ':1.2.840.113556.1.4.1941:' => $dn), - array($map['dn']) - ); - - // The search may throw an exception if no entries - // are found, unlikely but possible. - } catch (SimpleSAML_Error_UserNotFound $e) { - return array(); - } - - //Init the groups - $groups = array(); - - // Check each entry.. - foreach ($entries as $entry) { - - // Check for the DN using the original attribute name - if (isset($entry[$map['dn']][0])) { - $groups[] = $entry[$map['dn']][0]; - continue; - } - - // Sometimes the returned attribute names are lowercase - if (isset($entry[strtolower($map['dn'])][0])) { - $groups[] = $entry[strtolower($map['dn'])][0]; - continue; - } - - // AD queries also seem to return the objects dn by default - if (isset($entry['dn'])) { - $groups[] = $entry['dn']; - continue; - } - - // Could not find DN, log and continue - SimpleSAML\Logger::notice( - $this->title . 'The DN attribute [' . - implode(', ', array($map['dn'], strtolower($map['dn']), 'dn')) . - '] could not be found in the entry. ' . $this->var_export($entry) - ); - } - - // All done - return $groups; - } +class sspmod_ldap_Auth_Process_AttributeAddUsersGroups extends sspmod_ldap_Auth_Process_BaseFilter +{ + /** + * This is run when the filter is processed by SimpleSAML. + * It will attempt to find the current users groups using + * the best method possible for the LDAP product. The groups + * are then added to the request attributes. + * + * @throws SimpleSAML_Error_Exception + * @param $request + */ + public function process(&$request) + { + assert('is_array($request)'); + assert('array_key_exists("Attributes", $request)'); + + // Log the process + SimpleSAML\Logger::debug( + $this->title . 'Attempting to get the users groups...' + ); + + // Reference the attributes, just to make the names shorter + $attributes =& $request['Attributes']; + $map =& $this->attribute_map; + + // Get the users groups from LDAP + $groups = $this->getGroups($attributes); + + // Make the array if it is not set already + if (!isset($attributes[$map['groups']])) { + $attributes[$map['groups']] = array(); + } + + // Must be an array, else cannot merge groups + if (!is_array($attributes[$map['groups']])) { + throw new SimpleSAML_Error_Exception( + $this->title . 'The group attribute [' . $map['groups'] . + '] is not an array of group DNs. ' . $this->var_export($attributes[$map['groups']]) + ); + } + + // Add the users group(s) + $group_attribute =& $attributes[$map['groups']]; + $group_attribute = array_merge($group_attribute, $groups); + $group_attribute = array_unique($group_attribute); + + // All done + SimpleSAML\Logger::debug( + $this->title . 'Added users groups to the group attribute [' . + $map['groups'] . ']: ' . implode('; ', $groups) + ); + } + + + /** + * This section of code was broken out because the child + * filter AuthorizeByGroup can use this method as well. + * Based on the LDAP product, it will do an optimized search + * using the required attribute values from the user to + * get their group membership, recursively. + * + * @throws SimpleSAML_Error_Exception + * @param array $attributes + * @return array + */ + protected function getGroups(array $attributes) + { + // Reference the map, just to make the name shorter + $map =& $this->attribute_map; + + // Log the request + SimpleSAML\Logger::debug( + $this->title . 'Checking for groups based on the best method for the LDAP product.' + ); + + // Based on the directory service, search LDAP for groups + // If any attributes are needed, prepare them before calling search method + switch ($this->product) { + + case 'ACTIVEDIRECTORY': + + // Log the AD specific search + SimpleSAML\Logger::debug( + $this->title . 'Searching LDAP using ActiveDirectory specific method.' + ); + + // Make sure the defined dn attribute exists + if (!isset($attributes[$map['dn']])) { + throw new SimpleSAML_Error_Exception( + $this->title . 'The DN attribute [' . $map['dn'] . + '] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes)) + ); + } + + // DN attribute must have a value + if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) { + throw new SimpleSAML_Error_Exception( + $this->title . 'The DN attribute [' . $map['dn'] . + '] does not have a [0] value defined. ' . $this->var_export($attributes[$map['dn']]) + ); + } + + // Pass to the AD specific search + $groups = $this->searchActiveDirectory($attributes[$map['dn']][0]); + break; + + case 'OPENLDAP': + // Log the OpenLDAP specific search + SimpleSAML\Logger::debug( + $this->title . 'Searching LDAP using OpenLDAP specific method.' + ); + // Print group search string and search for all group names + $openldap_base = $this->config->getString('ldap.basedn','ou=groups,dc=example,dc=com'); + SimpleSAML\Logger::debug( + $this->title . "Searching for groups in ldap.basedn ".$openldap_base." with filter (".$map['memberof']."=".$attributes['uid'][0].") and attributes ".$map['member'] + ); + $groups = array(); + try { + // Intention is to filter in 'ou=groups,dc=example,dc=com' for '(memberUid = <UID>)' and take only the attributes 'cn' (=name of the group) + $all_groups = $this->getLdap()->searchformultiple( $openldap_base, array($map['memberof'] => $attributes['uid'][0]) , array($map['member'])); + } catch (SimpleSAML_Error_UserNotFound $e) { + break; // if no groups found return with empty (still just initialized) groups array + } + // run through all groups and add each to our groups array + foreach ($all_groups as $group_entry) { + $groups[] .= $group_entry[$map['member']][0]; + } + break; + + default: + + // Log the general search + SimpleSAML\Logger::debug( + $this->title . 'Searching LDAP using the default search method.' + ); + + // Make sure the defined memberOf attribute exists + if (!isset($attributes[$map['memberof']])) { + throw new SimpleSAML_Error_Exception( + $this->title . 'The memberof attribute [' . $map['memberof'] . + '] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes)) + ); + } + + // MemberOf must be an array of group DN's + if (!is_array($attributes[$map['memberof']])) { + throw new SimpleSAML_Error_Exception( + $this->title . 'The memberof attribute [' . $map['memberof'] . + '] is not an array of group DNs. ' . $this->var_export($attributes[$map['memberof']]) + ); + } + + // Search for the users group membership, recursively + $groups = $this->search($attributes[$map['memberof']]); + } + + // All done + SimpleSAML\Logger::debug( + $this->title . 'User found to be a member of the groups:' . implode('; ', $groups) + ); + return $groups; + } + + + /** + * Looks for groups from the list of DN's passed. Also + * recursively searches groups for further membership. + * Avoids loops by only searching a DN once. Returns + * the list of groups found. + * + * @param array $memberof + * @return array + */ + protected function search($memberof) + { + assert('is_array($memberof)'); + + // Used to determine what DN's have already been searched + static $searched = array(); + + // Init the groups variable + $groups = array(); + + // Shorten the variable name + $map =& $this->attribute_map; + + // Log the search + SimpleSAML\Logger::debug( + $this->title . 'Checking DNs for groups.' . + ' DNs: '. implode('; ', $memberof) . + ' Attributes: ' . $map['memberof'] . ', ' . $map['type'] . + ' Group Type: ' . $this->type_map['group'] + ); + + // Check each DN of the passed memberOf + foreach ($memberof as $dn) { + + // Avoid infinite loops, only need to check a DN once + if (isset($searched[$dn])) { + continue; + } + + // Track all DN's that are searched + // Use DN for key as well, isset() is faster than in_array() + $searched[$dn] = $dn; + + // Query LDAP for the attribute values for the DN + try { + $attributes = $this->getLdap()->getAttributes($dn, array($map['memberof'], $map['type'])); + } catch (SimpleSAML_Error_AuthSource $e) { + continue; // DN must not exist, just continue. Logged by the LDAP object + } + + // Only look for groups + if (!in_array($this->type_map['group'], $attributes[$map['type']])) { + continue; + } + + // Add to found groups array + $groups[] = $dn; + + // Recursively search "sub" groups + $groups = array_merge($groups, $this->search($attributes[$map['memberof']])); + } + + // Return only the unique group names + return array_unique($groups); + } + + + /** + * Searches LDAP using a ActiveDirectory specific filter, + * looking for group membership for the users DN. Returns + * the list of group DNs retrieved. + * + * @param string $dn + * @return array + */ + protected function searchActiveDirectory($dn) + { + assert('is_string($dn) && $dn != ""'); + + // Shorten the variable name + $map =& $this->attribute_map; + + // Log the search + SimpleSAML\Logger::debug( + $this->title . 'Searching ActiveDirectory group membership.' . + ' DN: ' . $dn . + ' DN Attribute: ' . $map['dn'] . + ' Member Attribute: ' . $map['member'] . + ' Type Attribute: ' . $map['type'] . + ' Type Value: ' . $this->type_map['group'] . + ' Base: ' . implode('; ', $this->base_dn) + ); + + // AD connections should have this set + $this->getLdap()->setOption(LDAP_OPT_REFERRALS, 0); + + // Search AD with the specific recursive flag + try { + $entries = $this->getLdap()->searchformultiple( + $this->base_dn, + array($map['type'] => $this->type_map['group'], $map['member'] . ':1.2.840.113556.1.4.1941:' => $dn), + array($map['dn']) + ); + + // The search may throw an exception if no entries + // are found, unlikely but possible. + } catch (SimpleSAML_Error_UserNotFound $e) { + return array(); + } + + //Init the groups + $groups = array(); + + // Check each entry.. + foreach ($entries as $entry) { + + // Check for the DN using the original attribute name + if (isset($entry[$map['dn']][0])) { + $groups[] = $entry[$map['dn']][0]; + continue; + } + + // Sometimes the returned attribute names are lowercase + if (isset($entry[strtolower($map['dn'])][0])) { + $groups[] = $entry[strtolower($map['dn'])][0]; + continue; + } + + // AD queries also seem to return the objects dn by default + if (isset($entry['dn'])) { + $groups[] = $entry['dn']; + continue; + } + + // Could not find DN, log and continue + SimpleSAML\Logger::notice( + $this->title . 'The DN attribute [' . + implode(', ', array($map['dn'], strtolower($map['dn']), 'dn')) . + '] could not be found in the entry. ' . $this->var_export($entry) + ); + } + + // All done + return $groups; + } } diff --git a/modules/ldap/lib/Auth/Process/BaseFilter.php b/modules/ldap/lib/Auth/Process/BaseFilter.php index 3d328e4..22aa197 100644 --- a/modules/ldap/lib/Auth/Process/BaseFilter.php +++ b/modules/ldap/lib/Auth/Process/BaseFilter.php @@ -8,270 +8,271 @@ * @author Ryan Panning <panman@traileyes.com> * @package SimpleSAMLphp */ -abstract class sspmod_ldap_Auth_Process_BaseFilter extends SimpleSAML_Auth_ProcessingFilter { - - /** - * List of attribute "alias's" linked to the real attribute - * name. Used for abstraction / configuration of the LDAP - * attribute names, which may change between dir service. - * - * @var array - */ - protected $attribute_map; - - - /** - * The base DN of the LDAP connection. Used when searching - * the LDAP server. - * - * @var string|array - */ - protected $base_dn; - - - /** - * The construct method will change the filter config into - * a SimpleSAML_Configuration object and store it here for - * later use, if needed. - * - * @var SimpleSAML_Configuration - */ - protected $config; - - - /** - * Instance, object of the ldap connection. Stored here to - * be access later during processing. - * - * @var sspmod_ldap_LdapConnection - */ - private $ldap; - - - /** - * Many times a LDAP product specific query can be used to - * speed up or reduce the filter process. This helps the - * child classes determine the product used to optimize - * those queries. - * - * @var string - */ - protected $product; - - - /** - * The class "title" used in logging and exception messages. - * This should be prepended to the beginning of the message. - * - * @var string - */ - protected $title = 'ldap:BaseFilter : '; - - - /** - * List of LDAP object types, used to determine the type of - * object that a DN references. - * - * @var array - */ - protected $type_map; - - - /** - * Checks the authsource, if defined, for configuration values - * to the LDAP server. Then sets up the LDAP connection for the - * instance/object and stores everything in class members. - * - * @throws SimpleSAML_Error_Exception - * @param array $config - * @param $reserved - */ - public function __construct(&$config, $reserved) { - parent::__construct($config, $reserved); - - // Change the class $title to match it's true name - // This way if the class is extended the proper name is used - $classname = get_class($this); - $classname = explode('_', $classname); - $this->title = 'ldap:' . end($classname) . ' : '; - - // Log the construction - SimpleSAML\Logger::debug( - $this->title . 'Creating and configuring the filter.' - ); - - // If an authsource was defined (an not empty string)... - if (isset($config['authsource']) && $config['authsource']) { - - // Log the authsource request - SimpleSAML\Logger::debug( - $this->title . 'Attempting to get configuration values from authsource [' . - $config['authsource'] . ']' - ); - - // Get the authsources file, which should contain the config - $authsource = SimpleSAML_Configuration::getConfig('authsources.php'); - - // Verify that the authsource config exists - if (!$authsource->hasValue($config['authsource'])) { - throw new SimpleSAML_Error_Exception( - $this->title . 'Authsource [' . $config['authsource'] . - '] defined in filter parameters not found in authsources.php' - ); - } - - // Get just the specified authsource config values - $authsource = $authsource->getConfigItem($config['authsource']); - $authsource = $authsource->toArray(); - - // Make sure it is an ldap source - // TODO: Support ldap:LDAPMulti, if possible - if (@$authsource[0] != 'ldap:LDAP') { - throw new SimpleSAML_Error_Exception( - $this->title . 'Authsource [' . $config['authsource'] . - '] specified in filter parameters is not an ldap:LDAP type' - ); - } - - // Build the authsource config - $authconfig = array(); - $authconfig['ldap.hostname'] = @$authsource['hostname']; - $authconfig['ldap.enable_tls'] = @$authsource['enable_tls']; - $authconfig['ldap.port'] = @$authsource['port']; - $authconfig['ldap.timeout'] = @$authsource['timeout']; - $authconfig['ldap.debug'] = @$authsource['debug']; - $authconfig['ldap.basedn'] = (@$authsource['search.enable'] ? @$authsource['search.base'] : NULL); - $authconfig['ldap.username'] = (@$authsource['search.enable'] ? @$authsource['search.username'] : NULL); - $authconfig['ldap.password'] = (@$authsource['search.enable'] ? @$authsource['search.password'] : NULL); - $authconfig['ldap.username'] = (@$authsource['priv.read'] ? @$authsource['priv.username'] : $authconfig['ldap.username']); - $authconfig['ldap.password'] = (@$authsource['priv.read'] ? @$authsource['priv.password'] : $authconfig['ldap.password']); - - // Only set the username attribute if the authsource specifies one attribute - if (@$authsource['search.enable'] && is_array(@$authsource['search.attributes']) - && count($authsource['search.attributes']) == 1) { - $authconfig['attribute.username'] = reset($authsource['search.attributes']); - } - - // Merge the authsource config with the filter config, - // but have the filter config override the authsource config - $config = array_merge($authconfig, $config); - - // Authsource complete - SimpleSAML\Logger::debug( - $this->title . 'Retrieved authsource [' . $config['authsource'] . - '] configuration values: ' . $this->var_export($authconfig) - ); - } - - // Convert the config array to a config class, - // that way we can verify type and define defaults. - // Store in the instance in-case needed later, by a child class. - $this->config = SimpleSAML_Configuration::loadFromArray($config, 'ldap:AuthProcess'); - - // Set all the filter values, setting defaults if needed - $this->base_dn = $this->config->getArrayizeString('ldap.basedn', ''); - $this->product = $this->config->getString('ldap.product', ''); - - // Cleanup the directory service, so that it is easier for - // child classes to determine service name consistently - $this->product = trim($this->product); - $this->product = strtoupper($this->product); - - // Log the member values retrieved above - SimpleSAML\Logger::debug( - $this->title . 'Configuration values retrieved;' . - ' BaseDN: ' . $this->var_export($this->base_dn) . - ' Product: ' . $this->var_export($this->product) - ); - - // Setup the attribute map which will be used to search LDAP - $this->attribute_map = array( - 'dn' => $this->config->getString('attribute.dn', 'distinguishedName'), - 'groups' => $this->config->getString('attribute.groups', 'groups'), - 'member' => $this->config->getString('attribute.member', 'member'), - 'memberof' => $this->config->getString('attribute.memberof', 'memberOf'), - 'name' => $this->config->getString('attribute.groupname', 'name'), - 'type' => $this->config->getString('attribute.type', 'objectClass'), - 'username' => $this->config->getString('attribute.username', 'sAMAccountName') - ); - - // Log the attribute map - SimpleSAML\Logger::debug( - $this->title . 'Attribute map created: ' . $this->var_export($this->attribute_map) - ); - - // Setup the object type map which is used to determine a DNs' type - $this->type_map = array( - 'group' => $this->config->getString('type.group', 'group'), - 'user' => $this->config->getString('type.user', 'user') - ); - - // Log the type map - SimpleSAML\Logger::debug( - $this->title . 'Type map created: ' . $this->var_export($this->type_map) - ); - } - - - /** - * Getter for the LDAP connection object. Created this getter - * rather than setting in the constructor to avoid unnecessarily - * connecting to LDAP when it might not be needed. - * - * @return sspmod_ldap_LdapConnection - */ - protected function getLdap() { - - // Check if already connected - if ($this->ldap) { - return $this->ldap; - } - - // Get the connection specific options - $hostname = $this->config->getString('ldap.hostname'); - $port = $this->config->getInteger('ldap.port', 389); - $enable_tls = $this->config->getBoolean('ldap.enable_tls', FALSE); - $debug = $this->config->getBoolean('ldap.debug', FALSE); - $timeout = $this->config->getInteger('ldap.timeout', 0); - $username = $this->config->getString('ldap.username', NULL); - $password = $this->config->getString('ldap.password', NULL); - - // Log the LDAP connection - SimpleSAML\Logger::debug( - $this->title . 'Connecting to LDAP server;' . - ' Hostname: ' . $hostname . - ' Port: ' . $port . - ' Enable TLS: ' . ($enable_tls ? 'Yes' : 'No') . - ' Debug: ' . ($debug ? 'Yes' : 'No') . - ' Timeout: ' . $timeout . - ' Username: ' . $username . - ' Password: ' . str_repeat('*', strlen($password)) - ); - - // Connect to the LDAP server to be queried during processing - $this->ldap = new SimpleSAML_Auth_LDAP($hostname, $enable_tls, $debug, $timeout, $port); - $this->ldap->bind($username, $password); - - // All done - return $this->ldap; - } - - - /** - * Local utility function to get details about a variable, - * basically converting it to a string to be used in a log - * message. The var_export() function returns several lines - * so this will remove the new lines and trim each line. - * - * @param mixed $value - * @return string - */ - protected function var_export($value) { - $export = var_export($value, TRUE); - $lines = explode("\n", $export); - foreach ($lines as &$line) { - $line = trim($line); - } - return implode(' ', $lines); - } +abstract class sspmod_ldap_Auth_Process_BaseFilter extends SimpleSAML_Auth_ProcessingFilter +{ + + /** + * List of attribute "alias's" linked to the real attribute + * name. Used for abstraction / configuration of the LDAP + * attribute names, which may change between dir service. + * + * @var array + */ + protected $attribute_map; + + + /** + * The base DN of the LDAP connection. Used when searching + * the LDAP server. + * + * @var string|array + */ + protected $base_dn; + + + /** + * The construct method will change the filter config into + * a SimpleSAML_Configuration object and store it here for + * later use, if needed. + * + * @var SimpleSAML_Configuration + */ + protected $config; + + + /** + * Instance, object of the ldap connection. Stored here to + * be access later during processing. + * + * @var sspmod_ldap_LdapConnection + */ + private $ldap; + + + /** + * Many times a LDAP product specific query can be used to + * speed up or reduce the filter process. This helps the + * child classes determine the product used to optimize + * those queries. + * + * @var string + */ + protected $product; + + + /** + * The class "title" used in logging and exception messages. + * This should be prepended to the beginning of the message. + * + * @var string + */ + protected $title = 'ldap:BaseFilter : '; + + + /** + * List of LDAP object types, used to determine the type of + * object that a DN references. + * + * @var array + */ + protected $type_map; + + + /** + * Checks the authsource, if defined, for configuration values + * to the LDAP server. Then sets up the LDAP connection for the + * instance/object and stores everything in class members. + * + * @throws SimpleSAML_Error_Exception + * @param array $config + * @param $reserved + */ + public function __construct(&$config, $reserved) + { + parent::__construct($config, $reserved); + + // Change the class $title to match it's true name + // This way if the class is extended the proper name is used + $classname = get_class($this); + $classname = explode('_', $classname); + $this->title = 'ldap:' . end($classname) . ' : '; + + // Log the construction + SimpleSAML\Logger::debug( + $this->title . 'Creating and configuring the filter.' + ); + + // If an authsource was defined (an not empty string)... + if (isset($config['authsource']) && $config['authsource']) { + + // Log the authsource request + SimpleSAML\Logger::debug( + $this->title . 'Attempting to get configuration values from authsource [' . + $config['authsource'] . ']' + ); + + // Get the authsources file, which should contain the config + $authsource = SimpleSAML_Configuration::getConfig('authsources.php'); + + // Verify that the authsource config exists + if (!$authsource->hasValue($config['authsource'])) { + throw new SimpleSAML_Error_Exception( + $this->title . 'Authsource [' . $config['authsource'] . + '] defined in filter parameters not found in authsources.php' + ); + } + + // Get just the specified authsource config values + $authsource = $authsource->getConfigItem($config['authsource']); + $authsource = $authsource->toArray(); + + // Make sure it is an ldap source + // TODO: Support ldap:LDAPMulti, if possible + if (@$authsource[0] != 'ldap:LDAP') { + throw new SimpleSAML_Error_Exception( + $this->title . 'Authsource [' . $config['authsource'] . + '] specified in filter parameters is not an ldap:LDAP type' + ); + } + + // Build the authsource config + $authconfig = array(); + $authconfig['ldap.hostname'] = @$authsource['hostname']; + $authconfig['ldap.enable_tls'] = @$authsource['enable_tls']; + $authconfig['ldap.port'] = @$authsource['port']; + $authconfig['ldap.timeout'] = @$authsource['timeout']; + $authconfig['ldap.debug'] = @$authsource['debug']; + $authconfig['ldap.basedn'] = (@$authsource['search.enable'] ? @$authsource['search.base'] : null); + $authconfig['ldap.username'] = (@$authsource['search.enable'] ? @$authsource['search.username'] : null); + $authconfig['ldap.password'] = (@$authsource['search.enable'] ? @$authsource['search.password'] : null); + $authconfig['ldap.username'] = (@$authsource['priv.read'] ? @$authsource['priv.username'] : $authconfig['ldap.username']); + $authconfig['ldap.password'] = (@$authsource['priv.read'] ? @$authsource['priv.password'] : $authconfig['ldap.password']); + + // Only set the username attribute if the authsource specifies one attribute + if (@$authsource['search.enable'] && is_array(@$authsource['search.attributes']) + && count($authsource['search.attributes']) == 1) { + $authconfig['attribute.username'] = reset($authsource['search.attributes']); + } + + // Merge the authsource config with the filter config, + // but have the filter config override the authsource config + $config = array_merge($authconfig, $config); + + // Authsource complete + SimpleSAML\Logger::debug( + $this->title . 'Retrieved authsource [' . $config['authsource'] . + '] configuration values: ' . $this->var_export($authconfig) + ); + } + + // Convert the config array to a config class, + // that way we can verify type and define defaults. + // Store in the instance in-case needed later, by a child class. + $this->config = SimpleSAML_Configuration::loadFromArray($config, 'ldap:AuthProcess'); + + // Set all the filter values, setting defaults if needed + $this->base_dn = $this->config->getArrayizeString('ldap.basedn', ''); + $this->product = $this->config->getString('ldap.product', ''); + + // Cleanup the directory service, so that it is easier for + // child classes to determine service name consistently + $this->product = trim($this->product); + $this->product = strtoupper($this->product); + + // Log the member values retrieved above + SimpleSAML\Logger::debug( + $this->title . 'Configuration values retrieved;' . + ' BaseDN: ' . $this->var_export($this->base_dn) . + ' Product: ' . $this->var_export($this->product) + ); + + // Setup the attribute map which will be used to search LDAP + $this->attribute_map = array( + 'dn' => $this->config->getString('attribute.dn', 'distinguishedName'), + 'groups' => $this->config->getString('attribute.groups', 'groups'), + 'member' => $this->config->getString('attribute.member', 'member'), + 'memberof' => $this->config->getString('attribute.memberof', 'memberOf'), + 'name' => $this->config->getString('attribute.groupname', 'name'), + 'type' => $this->config->getString('attribute.type', 'objectClass'), + 'username' => $this->config->getString('attribute.username', 'sAMAccountName') + ); + + // Log the attribute map + SimpleSAML\Logger::debug( + $this->title . 'Attribute map created: ' . $this->var_export($this->attribute_map) + ); + + // Setup the object type map which is used to determine a DNs' type + $this->type_map = array( + 'group' => $this->config->getString('type.group', 'group'), + 'user' => $this->config->getString('type.user', 'user') + ); + + // Log the type map + SimpleSAML\Logger::debug( + $this->title . 'Type map created: ' . $this->var_export($this->type_map) + ); + } + + /** + * Getter for the LDAP connection object. Created this getter + * rather than setting in the constructor to avoid unnecessarily + * connecting to LDAP when it might not be needed. + * + * @return sspmod_ldap_LdapConnection + */ + protected function getLdap() + { + // Check if already connected + if ($this->ldap) { + return $this->ldap; + } + + // Get the connection specific options + $hostname = $this->config->getString('ldap.hostname'); + $port = $this->config->getInteger('ldap.port', 389); + $enable_tls = $this->config->getBoolean('ldap.enable_tls', false); + $debug = $this->config->getBoolean('ldap.debug', false); + $timeout = $this->config->getInteger('ldap.timeout', 0); + $username = $this->config->getString('ldap.username', null); + $password = $this->config->getString('ldap.password', null); + + // Log the LDAP connection + SimpleSAML\Logger::debug( + $this->title . 'Connecting to LDAP server;' . + ' Hostname: ' . $hostname . + ' Port: ' . $port . + ' Enable TLS: ' . ($enable_tls ? 'Yes' : 'No') . + ' Debug: ' . ($debug ? 'Yes' : 'No') . + ' Timeout: ' . $timeout . + ' Username: ' . $username . + ' Password: ' . str_repeat('*', strlen($password)) + ); + + // Connect to the LDAP server to be queried during processing + $this->ldap = new SimpleSAML_Auth_LDAP($hostname, $enable_tls, $debug, $timeout, $port); + $this->ldap->bind($username, $password); + + // All done + return $this->ldap; + } + + /** + * Local utility function to get details about a variable, + * basically converting it to a string to be used in a log + * message. The var_export() function returns several lines + * so this will remove the new lines and trim each line. + * + * @param mixed $value + * @return string + */ + protected function var_export($value) + { + $export = var_export($value, true); + $lines = explode("\n", $export); + foreach ($lines as &$line) { + $line = trim($line); + } + return implode(' ', $lines); + } } diff --git a/modules/ldap/lib/Auth/Source/LDAP.php b/modules/ldap/lib/Auth/Source/LDAP.php index 83b35fa..7bf979a 100644 --- a/modules/ldap/lib/Auth/Source/LDAP.php +++ b/modules/ldap/lib/Auth/Source/LDAP.php @@ -10,45 +10,48 @@ * * @package SimpleSAMLphp */ -class sspmod_ldap_Auth_Source_LDAP extends sspmod_core_Auth_UserPassBase { - - /** - * A LDAP configuration object. - */ - private $ldapConfig; - - - /** - * Constructor for this authentication source. - * - * @param array $info Information about this authentication source. - * @param array $config Configuration. - */ - public function __construct($info, $config) { - assert('is_array($info)'); - assert('is_array($config)'); - - // Call the parent constructor first, as required by the interface - parent::__construct($info, $config); - - $this->ldapConfig = new sspmod_ldap_ConfigHelper($config, - 'Authentication source ' . var_export($this->authId, TRUE)); - } - - - /** - * Attempt to log in using the given username and password. - * - * @param string $username The username the user wrote. - * @param string $password The password the user wrote. - * param array $sasl_arg Associative array of SASL options - * @return array Associative array with the users attributes. - */ - protected function login($username, $password, array $sasl_args = NULL) { - assert('is_string($username)'); - assert('is_string($password)'); - - return $this->ldapConfig->login($username, $password, $sasl_args); - } +class sspmod_ldap_Auth_Source_LDAP extends sspmod_core_Auth_UserPassBase +{ + + /** + * A LDAP configuration object. + */ + private $ldapConfig; + + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) + { + assert('is_array($info)'); + assert('is_array($config)'); + + // Call the parent constructor first, as required by the interface + parent::__construct($info, $config); + + $this->ldapConfig = new sspmod_ldap_ConfigHelper($config, + 'Authentication source ' . var_export($this->authId, true)); + } + + + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * param array $sasl_arg Associative array of SASL options + * @return array Associative array with the users attributes. + */ + protected function login($username, $password, array $sasl_args = null) + { + assert('is_string($username)'); + assert('is_string($password)'); + + return $this->ldapConfig->login($username, $password, $sasl_args); + } } diff --git a/modules/ldap/lib/Auth/Source/LDAPMulti.php b/modules/ldap/lib/Auth/Source/LDAPMulti.php index 4f5adaf..e38118e 100644 --- a/modules/ldap/lib/Auth/Source/LDAPMulti.php +++ b/modules/ldap/lib/Auth/Source/LDAPMulti.php @@ -10,112 +10,115 @@ * * @package SimpleSAMLphp */ -class sspmod_ldap_Auth_Source_LDAPMulti extends sspmod_core_Auth_UserPassOrgBase { - - /** - * An array with descriptions for organizations. - */ - private $orgs; - - /** - * An array of organization IDs to LDAP configuration objects. - */ - private $ldapOrgs; - - /** - * Whether we should include the organization as part of the username. - */ - private $includeOrgInUsername; - - - /** - * Constructor for this authentication source. - * - * @param array $info Information about this authentication source. - * @param array $config Configuration. - */ - public function __construct($info, $config) { - assert('is_array($info)'); - assert('is_array($config)'); - - // Call the parent constructor first, as required by the interface - parent::__construct($info, $config); - - $cfgHelper = SimpleSAML_Configuration::loadFromArray($config, - 'Authentication source ' . var_export($this->authId, TRUE)); - - - $this->orgs = array(); - $this->ldapOrgs = array(); - foreach ($config as $name => $value) { - - if ($name === 'username_organization_method') { - $usernameOrgMethod = $cfgHelper->getValueValidate( - 'username_organization_method', - array('none', 'allow', 'force')); - $this->setUsernameOrgMethod($usernameOrgMethod); - continue; - } - - if ($name === 'include_organization_in_username') { - $this->includeOrgInUsername = $cfgHelper->getBoolean( - 'include_organization_in_username', FALSE); - continue; - } - - $orgCfg = $cfgHelper->getArray($name); - $orgId = $name; - - if (array_key_exists('description', $orgCfg)) { - $this->orgs[$orgId] = $orgCfg['description']; - } else { - $this->orgs[$orgId] = $orgId; - } - - $orgCfg = new sspmod_ldap_ConfigHelper($orgCfg, - 'Authentication source ' . var_export($this->authId, TRUE) . - ', organization ' . var_export($orgId, TRUE)); - $this->ldapOrgs[$orgId] = $orgCfg; - } - } - - - /** - * Attempt to log in using the given username and password. - * - * @param string $username The username the user wrote. - * @param string $password The password the user wrote. - * @param string $org The organization the user chose. - * @return array Associative array with the users attributes. - */ - protected function login($username, $password, $org, array $sasl_args = NULL) { - assert('is_string($username)'); - assert('is_string($password)'); - assert('is_string($org)'); - - if (!array_key_exists($org, $this->ldapOrgs)) { - // The user has selected an organization which doesn't exist anymore. - SimpleSAML\Logger::warning('Authentication source ' . var_export($this->authId, TRUE) . - ': Organization seems to have disappeared while the user logged in.' . - ' Organization was ' . var_export($org, TRUE)); - throw new SimpleSAML_Error_Error('WRONGUSERPASS'); - } - - if ($this->includeOrgInUsername) { - $username = $username . '@' . $org; - } - - return $this->ldapOrgs[$org]->login($username, $password, $sasl_args); - } - - - /** - * Retrieve list of organizations. - * - * @return array Associative array with the organizations. - */ - protected function getOrganizations() { - return $this->orgs; - } - +class sspmod_ldap_Auth_Source_LDAPMulti extends sspmod_core_Auth_UserPassOrgBase +{ + + /** + * An array with descriptions for organizations. + */ + private $orgs; + + /** + * An array of organization IDs to LDAP configuration objects. + */ + private $ldapOrgs; + + /** + * Whether we should include the organization as part of the username. + */ + private $includeOrgInUsername; + + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) + { + assert('is_array($info)'); + assert('is_array($config)'); + + // Call the parent constructor first, as required by the interface + parent::__construct($info, $config); + + $cfgHelper = SimpleSAML_Configuration::loadFromArray($config, + 'Authentication source ' . var_export($this->authId, true)); + + + $this->orgs = array(); + $this->ldapOrgs = array(); + foreach ($config as $name => $value) { + + if ($name === 'username_organization_method') { + $usernameOrgMethod = $cfgHelper->getValueValidate( + 'username_organization_method', + array('none', 'allow', 'force')); + $this->setUsernameOrgMethod($usernameOrgMethod); + continue; + } + + if ($name === 'include_organization_in_username') { + $this->includeOrgInUsername = $cfgHelper->getBoolean( + 'include_organization_in_username', false); + continue; + } + + $orgCfg = $cfgHelper->getArray($name); + $orgId = $name; + + if (array_key_exists('description', $orgCfg)) { + $this->orgs[$orgId] = $orgCfg['description']; + } else { + $this->orgs[$orgId] = $orgId; + } + + $orgCfg = new sspmod_ldap_ConfigHelper($orgCfg, + 'Authentication source ' . var_export($this->authId, true) . + ', organization ' . var_export($orgId, true)); + $this->ldapOrgs[$orgId] = $orgCfg; + } + } + + + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @param string $org The organization the user chose. + * @return array Associative array with the users attributes. + */ + protected function login($username, $password, $org, array $sasl_args = null) + { + assert('is_string($username)'); + assert('is_string($password)'); + assert('is_string($org)'); + + if (!array_key_exists($org, $this->ldapOrgs)) { + // The user has selected an organization which doesn't exist anymore. + SimpleSAML\Logger::warning('Authentication source ' . var_export($this->authId, true) . + ': Organization seems to have disappeared while the user logged in.' . + ' Organization was ' . var_export($org, true)); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + + if ($this->includeOrgInUsername) { + $username = $username . '@' . $org; + } + + return $this->ldapOrgs[$org]->login($username, $password, $sasl_args); + } + + + /** + * Retrieve list of organizations. + * + * @return array Associative array with the organizations. + */ + protected function getOrganizations() + { + return $this->orgs; + } } diff --git a/modules/ldap/lib/ConfigHelper.php b/modules/ldap/lib/ConfigHelper.php index 7c8802d..28e4151 100644 --- a/modules/ldap/lib/ConfigHelper.php +++ b/modules/ldap/lib/ConfigHelper.php @@ -8,292 +8,297 @@ * * @package SimpleSAMLphp */ -class sspmod_ldap_ConfigHelper { +class sspmod_ldap_ConfigHelper +{ + /** + * String with the location of this configuration. + * Used for error reporting. + */ + private $location; - /** - * String with the location of this configuration. - * Used for error reporting. - */ - private $location; + /** + * The hostname of the LDAP server. + */ + private $hostname; - /** - * The hostname of the LDAP server. - */ - private $hostname; + /** + * Whether we should use TLS/SSL when contacting the LDAP server. + */ + private $enableTLS; - /** - * Whether we should use TLS/SSL when contacting the LDAP server. - */ - private $enableTLS; + /** + * Whether debug output is enabled. + * + * @var bool + */ + private $debug; - /** - * Whether debug output is enabled. - * - * @var bool - */ - private $debug; + /** + * The timeout for accessing the LDAP server. + * + * @var int + */ + private $timeout; + /** + * The port used when accessing the LDAP server. + * + * @var int + */ + private $port; - /** - * The timeout for accessing the LDAP server. - * - * @var int - */ - private $timeout; + /** + * Whether to follow referrals + */ + private $referrals; - /** - * The port used when accessing the LDAP server. - * - * @var int - */ - private $port; - /** - * Whether to follow referrals - */ - private $referrals; + /** + * Whether we need to search for the users DN. + */ + private $searchEnable; - /** - * Whether we need to search for the users DN. - */ - private $searchEnable; + /** + * The username we should bind with before we can search for the user. + */ + private $searchUsername; - /** - * The username we should bind with before we can search for the user. - */ - private $searchUsername; + /** + * The password we should bind with before we can search for the user. + */ + private $searchPassword; - /** - * The password we should bind with before we can search for the user. - */ - private $searchPassword; - - - /** - * Array with the base DN(s) for the search. - */ - private $searchBase; - - /** - * Additional LDAP filter fields for the search - */ - private $searchFilter; - - /** - * The attributes which should match the username. - */ - private $searchAttributes; - - - /** - * The DN pattern we should use to create the DN from the username. - */ - private $dnPattern; - - - /** - * The attributes we should fetch. Can be NULL in which case we will fetch all attributes. - */ - private $attributes; - - - /** - * The user cannot get all attributes, privileged reader required - */ - private $privRead; - - - /** - * The DN we should bind with before we can get the attributes. - */ - private $privUsername; - - - /** - * The password we should bind with before we can get the attributes. - */ - private $privPassword; - - - /** - * Constructor for this configuration parser. - * - * @param array $config Configuration. - * @param string $location The location of this configuration. Used for error reporting. - */ - public function __construct($config, $location) { - assert('is_array($config)'); - assert('is_string($location)'); - - $this->location = $location; - - // Parse configuration - $config = SimpleSAML_Configuration::loadFromArray($config, $location); - - $this->hostname = $config->getString('hostname'); - $this->enableTLS = $config->getBoolean('enable_tls', FALSE); - $this->debug = $config->getBoolean('debug', FALSE); - $this->timeout = $config->getInteger('timeout', 0); - $this->port = $config->getInteger('port', 389); - $this->referrals = $config->getBoolean('referrals', TRUE); - $this->searchEnable = $config->getBoolean('search.enable', FALSE); - $this->privRead = $config->getBoolean('priv.read', FALSE); - - if ($this->searchEnable) { - $this->searchUsername = $config->getString('search.username', NULL); - if ($this->searchUsername !== NULL) { - $this->searchPassword = $config->getString('search.password'); - } - - $this->searchBase = $config->getArrayizeString('search.base'); - $this->searchFilter = $config->getString('search.filter',NULL); - $this->searchAttributes = $config->getArray('search.attributes'); - - } else { - $this->dnPattern = $config->getString('dnpattern'); - } - - // Are privs needed to get to the attributes? - if ($this->privRead) { - $this->privUsername = $config->getString('priv.username'); - $this->privPassword = $config->getString('priv.password'); - } - - $this->attributes = $config->getArray('attributes', NULL); - } - - - /** - * Attempt to log in using the given username and password. - * - * Will throw a SimpleSAML_Error_Error('WRONGUSERPASS') if the username or password is wrong. - * If there is a configuration problem, an Exception will be thrown. - * - * @param string $username The username the user wrote. - * @param string $password The password the user wrote. - * @param arrray $sasl_args Array of SASL options for LDAP bind. - * @return array Associative array with the users attributes. - */ - public function login($username, $password, array $sasl_args = NULL) { - assert('is_string($username)'); - assert('is_string($password)'); - - if (empty($password)) { - SimpleSAML\Logger::info($this->location . ': Login with empty password disallowed.'); - throw new SimpleSAML_Error_Error('WRONGUSERPASS'); - } - - $ldap = new SimpleSAML_Auth_LDAP($this->hostname, $this->enableTLS, $this->debug, $this->timeout, $this->port, $this->referrals); - - if (!$this->searchEnable) { - $ldapusername = addcslashes($username, ',+"\\<>;*'); - $dn = str_replace('%username%', $ldapusername, $this->dnPattern); - } else { - if ($this->searchUsername !== NULL) { - if(!$ldap->bind($this->searchUsername, $this->searchPassword)) { - throw new Exception('Error authenticating using search username & password.'); - } - } - - $dn = $ldap->searchfordn($this->searchBase, $this->searchAttributes, $username, TRUE, $this->searchFilter); - if ($dn === NULL) { - /* User not found with search. */ - SimpleSAML\Logger::info($this->location . ': Unable to find users DN. username=\'' . $username . '\''); - throw new SimpleSAML_Error_Error('WRONGUSERPASS'); - } - } - - if (!$ldap->bind($dn, $password, $sasl_args)) { - SimpleSAML\Logger::info($this->location . ': '. $username . ' failed to authenticate. DN=' . $dn); - throw new SimpleSAML_Error_Error('WRONGUSERPASS'); - } - - /* In case of SASL bind, authenticated and authorized DN may differ */ - if (isset($sasl_args)) - $dn = $ldap->whoami($this->searchBase, $this->searchAttributes); - - /* Are privs needed to get the attributes? */ - if ($this->privRead) { - /* Yes, rebind with privs */ - if(!$ldap->bind($this->privUsername, $this->privPassword)) { - throw new Exception('Error authenticating using privileged DN & password.'); - } - } - - return $ldap->getAttributes($dn, $this->attributes); - } - - - /** - * Search for a DN. - * - * @param string|array $attribute - * The attribute name(s) searched for. If set to NULL, values from - * configuration is used. - * @param string $value - * The attribute value searched for. - * @param bool $allowZeroHits - * Determines if the method will throw an exception if no - * hits are found. Defaults to FALSE. - * @return string - * The DN of the matching element, if found. If no element was - * found and $allowZeroHits is set to FALSE, an exception will - * be thrown; otherwise NULL will be returned. - * @throws SimpleSAML_Error_AuthSource if: - * - LDAP search encounter some problems when searching cataloge - * - Not able to connect to LDAP server - * @throws SimpleSAML_Error_UserNotFound if: - * - $allowZeroHits er TRUE and no result is found - * - */ - public function searchfordn($attribute, $value, $allowZeroHits) { - $ldap = new SimpleSAML_Auth_LDAP($this->hostname, - $this->enableTLS, - $this->debug, - $this->timeout, - $this->port, - $this->referrals); - - if ($attribute == NULL) - $attribute = $this->searchAttributes; - - if ($this->searchUsername !== NULL) { - if(!$ldap->bind($this->searchUsername, $this->searchPassword)) { - throw new Exception('Error authenticating using search username & password.'); - } - } - - return $ldap->searchfordn($this->searchBase, $attribute, - $value, $allowZeroHits); - } - - public function getAttributes($dn, $attributes = NULL) { - if ($attributes == NULL) - $attributes = $this->attributes; - - $ldap = new SimpleSAML_Auth_LDAP($this->hostname, - $this->enableTLS, - $this->debug, - $this->timeout, - $this->port, - $this->referrals); - - /* Are privs needed to get the attributes? */ - if ($this->privRead) { - /* Yes, rebind with privs */ - if(!$ldap->bind($this->privUsername, $this->privPassword)) { - throw new Exception('Error authenticating using privileged DN & password.'); - } - } - - return $ldap->getAttributes($dn, $attributes); - } + /** + * Array with the base DN(s) for the search. + */ + private $searchBase; + + /** + * Additional LDAP filter fields for the search + */ + private $searchFilter; + + /** + * The attributes which should match the username. + */ + private $searchAttributes; + + + /** + * The DN pattern we should use to create the DN from the username. + */ + private $dnPattern; + + + /** + * The attributes we should fetch. Can be NULL in which case we will fetch all attributes. + */ + private $attributes; + + + /** + * The user cannot get all attributes, privileged reader required + */ + private $privRead; + + + /** + * The DN we should bind with before we can get the attributes. + */ + private $privUsername; + + + /** + * The password we should bind with before we can get the attributes. + */ + private $privPassword; + + + /** + * Constructor for this configuration parser. + * + * @param array $config Configuration. + * @param string $location The location of this configuration. Used for error reporting. + */ + public function __construct($config, $location) + { + assert('is_array($config)'); + assert('is_string($location)'); + + $this->location = $location; + + // Parse configuration + $config = SimpleSAML_Configuration::loadFromArray($config, $location); + + $this->hostname = $config->getString('hostname'); + $this->enableTLS = $config->getBoolean('enable_tls', false); + $this->debug = $config->getBoolean('debug', false); + $this->timeout = $config->getInteger('timeout', 0); + $this->port = $config->getInteger('port', 389); + $this->referrals = $config->getBoolean('referrals', true); + $this->searchEnable = $config->getBoolean('search.enable', false); + $this->privRead = $config->getBoolean('priv.read', false); + + if ($this->searchEnable) { + $this->searchUsername = $config->getString('search.username', null); + if ($this->searchUsername !== null) { + $this->searchPassword = $config->getString('search.password'); + } + + $this->searchBase = $config->getArrayizeString('search.base'); + $this->searchFilter = $config->getString('search.filter', null); + $this->searchAttributes = $config->getArray('search.attributes'); + + } else { + $this->dnPattern = $config->getString('dnpattern'); + } + + // Are privs needed to get to the attributes? + if ($this->privRead) { + $this->privUsername = $config->getString('priv.username'); + $this->privPassword = $config->getString('priv.password'); + } + + $this->attributes = $config->getArray('attributes', null); + } + + + /** + * Attempt to log in using the given username and password. + * + * Will throw a SimpleSAML_Error_Error('WRONGUSERPASS') if the username or password is wrong. + * If there is a configuration problem, an Exception will be thrown. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @param arrray $sasl_args Array of SASL options for LDAP bind. + * @return array Associative array with the users attributes. + */ + public function login($username, $password, array $sasl_args = null) + { + assert('is_string($username)'); + assert('is_string($password)'); + + if (empty($password)) { + SimpleSAML\Logger::info($this->location . ': Login with empty password disallowed.'); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + + $ldap = new SimpleSAML_Auth_LDAP($this->hostname, $this->enableTLS, $this->debug, $this->timeout, $this->port, $this->referrals); + + if (!$this->searchEnable) { + $ldapusername = addcslashes($username, ',+"\\<>;*'); + $dn = str_replace('%username%', $ldapusername, $this->dnPattern); + } else { + if ($this->searchUsername !== null) { + if (!$ldap->bind($this->searchUsername, $this->searchPassword)) { + throw new Exception('Error authenticating using search username & password.'); + } + } + + $dn = $ldap->searchfordn($this->searchBase, $this->searchAttributes, $username, true, $this->searchFilter); + if ($dn === null) { + /* User not found with search. */ + SimpleSAML\Logger::info($this->location . ': Unable to find users DN. username=\'' . $username . '\''); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + } + + if (!$ldap->bind($dn, $password, $sasl_args)) { + SimpleSAML\Logger::info($this->location . ': '. $username . ' failed to authenticate. DN=' . $dn); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + + /* In case of SASL bind, authenticated and authorized DN may differ */ + if (isset($sasl_args)) { + $dn = $ldap->whoami($this->searchBase, $this->searchAttributes); + } + + /* Are privs needed to get the attributes? */ + if ($this->privRead) { + /* Yes, rebind with privs */ + if (!$ldap->bind($this->privUsername, $this->privPassword)) { + throw new Exception('Error authenticating using privileged DN & password.'); + } + } + + return $ldap->getAttributes($dn, $this->attributes); + } + + + /** + * Search for a DN. + * + * @param string|array $attribute + * The attribute name(s) searched for. If set to NULL, values from + * configuration is used. + * @param string $value + * The attribute value searched for. + * @param bool $allowZeroHits + * Determines if the method will throw an exception if no + * hits are found. Defaults to FALSE. + * @return string + * The DN of the matching element, if found. If no element was + * found and $allowZeroHits is set to FALSE, an exception will + * be thrown; otherwise NULL will be returned. + * @throws SimpleSAML_Error_AuthSource if: + * - LDAP search encounter some problems when searching cataloge + * - Not able to connect to LDAP server + * @throws SimpleSAML_Error_UserNotFound if: + * - $allowZeroHits is FALSE and no result is found + * + */ + public function searchfordn($attribute, $value, $allowZeroHits) + { + $ldap = new SimpleSAML_Auth_LDAP($this->hostname, + $this->enableTLS, + $this->debug, + $this->timeout, + $this->port, + $this->referrals); + + if ($attribute == null) { + $attribute = $this->searchAttributes; + } + + if ($this->searchUsername !== null) { + if (!$ldap->bind($this->searchUsername, $this->searchPassword)) { + throw new Exception('Error authenticating using search username & password.'); + } + } + + return $ldap->searchfordn($this->searchBase, $attribute, + $value, $allowZeroHits); + } + + public function getAttributes($dn, $attributes = null) + { + if ($attributes == null) { + $attributes = $this->attributes; + } + + $ldap = new SimpleSAML_Auth_LDAP($this->hostname, + $this->enableTLS, + $this->debug, + $this->timeout, + $this->port, + $this->referrals); + + /* Are privs needed to get the attributes? */ + if ($this->privRead) { + /* Yes, rebind with privs */ + if (!$ldap->bind($this->privUsername, $this->privPassword)) { + throw new Exception('Error authenticating using privileged DN & password.'); + } + } + return $ldap->getAttributes($dn, $attributes); + } } diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index 8fbf235..92e5363 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -1,1054 +1,1149 @@ <?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; + } +} diff --git a/modules/sanitycheck/config-templates/config-sanitycheck.php b/modules/sanitycheck/config-templates/config-sanitycheck.php index 748a21b..ac40a0f 100644 --- a/modules/sanitycheck/config-templates/config-sanitycheck.php +++ b/modules/sanitycheck/config-templates/config-sanitycheck.php @@ -5,12 +5,12 @@ $config = array ( - /* - * Do you want to generate statistics using the cron module? If so, specify which cron tag to use. - * Examples: daily, weekly - * To not run statistics in cron, set value to - * 'cron_tag' => NULL, - */ - 'cron_tag' => 'hourly', + /* + * Do you want to generate statistics using the cron module? If so, specify which cron tag to use. + * Examples: daily, weekly + * To not run statistics in cron, set value to + * 'cron_tag' => null, + */ + 'cron_tag' => 'hourly', ); diff --git a/modules/sanitycheck/hooks/hook_cron.php b/modules/sanitycheck/hooks/hook_cron.php index 37c020e..44abd49 100644 --- a/modules/sanitycheck/hooks/hook_cron.php +++ b/modules/sanitycheck/hooks/hook_cron.php @@ -4,39 +4,38 @@ * * @param array &$croninfo Output */ -function sanitycheck_hook_cron(&$croninfo) { - assert('is_array($croninfo)'); - assert('array_key_exists("summary", $croninfo)'); - assert('array_key_exists("tag", $croninfo)'); +function sanitycheck_hook_cron(&$croninfo) +{ + assert('is_array($croninfo)'); + assert('array_key_exists("summary", $croninfo)'); + assert('array_key_exists("tag", $croninfo)'); - SimpleSAML\Logger::info('cron [sanitycheck]: Running cron in cron tag [' . $croninfo['tag'] . '] '); + SimpleSAML\Logger::info('cron [sanitycheck]: Running cron in cron tag [' . $croninfo['tag'] . '] '); - try { - - $sconfig = SimpleSAML_Configuration::getOptionalConfig('config-sanitycheck.php'); + try { + $sconfig = SimpleSAML_Configuration::getOptionalConfig('config-sanitycheck.php'); - $cronTag = $sconfig->getString('cron_tag', NULL); - if ($cronTag === NULL || $cronTag !== $croninfo['tag']) { - return; - } + $cronTag = $sconfig->getString('cron_tag', null); + if ($cronTag === null || $cronTag !== $croninfo['tag']) { + return; + } - $info = array(); - $errors = array(); - $hookinfo = array( - 'info' => &$info, - 'errors' => &$errors, - ); - - SimpleSAML\Module::callHooks('sanitycheck', $hookinfo); - - if (count($errors) > 0) { - foreach ($errors AS $err) { - $croninfo['summary'][] = 'Sanitycheck error: ' . $err; - } - } - - } catch (Exception $e) { - $croninfo['summary'][] = 'Error executing sanity check: ' . $e->getMessage(); - } + $info = array(); + $errors = array(); + $hookinfo = array( + 'info' => &$info, + 'errors' => &$errors, + ); + SimpleSAML\Module::callHooks('sanitycheck', $hookinfo); + + if (count($errors) > 0) { + foreach ($errors AS $err) { + $croninfo['summary'][] = 'Sanitycheck error: ' . $err; + } + } + + } catch (Exception $e) { + $croninfo['summary'][] = 'Error executing sanity check: ' . $e->getMessage(); + } } diff --git a/modules/sanitycheck/hooks/hook_frontpage.php b/modules/sanitycheck/hooks/hook_frontpage.php index 27d55ab..b40e657 100644 --- a/modules/sanitycheck/hooks/hook_frontpage.php +++ b/modules/sanitycheck/hooks/hook_frontpage.php @@ -4,14 +4,14 @@ * * @param array &$links The links on the frontpage, split into sections. */ -function sanitycheck_hook_frontpage(&$links) { - assert('is_array($links)'); - assert('array_key_exists("links", $links)'); - - $links['config']['santitycheck'] = array( - 'href' => SimpleSAML\Module::getModuleURL('sanitycheck/index.php'), - 'text' => array('en' => 'Sanity check of your SimpleSAMLphp setup'), - 'shorttext' => array('en' => 'SanityCheck'), - ); +function sanitycheck_hook_frontpage(&$links) +{ + assert('is_array($links)'); + assert('array_key_exists("links", $links)'); + $links['config']['santitycheck'] = array( + 'href' => SimpleSAML\Module::getModuleURL('sanitycheck/index.php'), + 'text' => array('en' => 'Sanity check of your SimpleSAMLphp setup'), + 'shorttext' => array('en' => 'SanityCheck'), + ); } diff --git a/modules/sanitycheck/hooks/hook_moduleinfo.php b/modules/sanitycheck/hooks/hook_moduleinfo.php index c186727..679ac17 100644 --- a/modules/sanitycheck/hooks/hook_moduleinfo.php +++ b/modules/sanitycheck/hooks/hook_moduleinfo.php @@ -4,16 +4,16 @@ * * @param array &$moduleinfo The links on the frontpage, split into sections. */ -function sanitycheck_hook_moduleinfo(&$moduleinfo) { - assert('is_array($moduleinfo)'); - assert('array_key_exists("info", $moduleinfo)'); +function sanitycheck_hook_moduleinfo(&$moduleinfo) +{ + assert('is_array($moduleinfo)'); + assert('array_key_exists("info", $moduleinfo)'); - $moduleinfo['info']['sanitycheck'] = array( - 'name' => array('en' => 'Sanity check'), - 'description' => array('en' => 'This module adds functionality for other modules to provide santity checks.'), - - 'dependencies' => array('core'), - 'uses' => array('cron'), - ); + $moduleinfo['info']['sanitycheck'] = array( + 'name' => array('en' => 'Sanity check'), + 'description' => array('en' => 'This module adds functionality for other modules to provide santity checks.'), + 'dependencies' => array('core'), + 'uses' => array('cron'), + ); } diff --git a/modules/sanitycheck/hooks/hook_sanitycheck.php b/modules/sanitycheck/hooks/hook_sanitycheck.php index c15f1c6..867eab5 100644 --- a/modules/sanitycheck/hooks/hook_sanitycheck.php +++ b/modules/sanitycheck/hooks/hook_sanitycheck.php @@ -4,11 +4,11 @@ * * @param array &$hookinfo hookinfo */ -function sanitycheck_hook_sanitycheck(&$hookinfo) { - assert('is_array($hookinfo)'); - assert('array_key_exists("errors", $hookinfo)'); - assert('array_key_exists("info", $hookinfo)'); +function sanitycheck_hook_sanitycheck(&$hookinfo) +{ + assert('is_array($hookinfo)'); + assert('array_key_exists("errors", $hookinfo)'); + assert('array_key_exists("info", $hookinfo)'); - $hookinfo['info'][] = '[sanitycheck] At least the sanity check itself is working :)'; - + $hookinfo['info'][] = '[sanitycheck] At least the sanity check itself is working :)'; } diff --git a/modules/sanitycheck/www/index.php b/modules/sanitycheck/www/index.php index 708348b..e365db2 100644 --- a/modules/sanitycheck/www/index.php +++ b/modules/sanitycheck/www/index.php @@ -1,25 +1,23 @@ <?php - $config = SimpleSAML_Configuration::getInstance(); $info = array(); $errors = array(); $hookinfo = array( - 'info' => &$info, - 'errors' => &$errors, + 'info' => &$info, + 'errors' => &$errors, ); SimpleSAML\Module::callHooks('sanitycheck', $hookinfo); - if (isset($_REQUEST['output']) && $_REQUEST['output'] == 'text') { - - if (count($errors) === 0) { - echo 'OK'; - } else { - echo 'FAIL'; - } - exit; + + if (count($errors) === 0) { + echo 'OK'; + } else { + echo 'FAIL'; + } + exit; } $t = new SimpleSAML_XHTML_Template($config, 'sanitycheck:check.tpl.php'); |