diff --git a/.coveralls.yml b/.coveralls.yml index 173ff356..d43702dd 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,7 +1,5 @@ service_name: travis-ci -src_dir: lib - coverage_clover: tests/build/logs/clover.xml json_path: tests/build/logs/coveralls-upload.json diff --git a/.travis.yml b/.travis.yml index 648e8cc0..c8ddc1d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: php +cache: + directories: + - $HOME/.composer/cache + php: - 5.6 - 5.5 diff --git a/lib/Saml2/AttributePolicyHelpers.php b/lib/Saml2/AttributePolicyHelpers.php new file mode 100644 index 00000000..74b4ad9d --- /dev/null +++ b/lib/Saml2/AttributePolicyHelpers.php @@ -0,0 +1,31 @@ +_settings->getSPData(); + $attributeMap = $spData['attributeMap']; + $attributePolicy = $spData['attributePolicy']; + $attributes = $this->_applyAttributeMapping($attributeMap, $attributes); + $attributes = $this->_applyAttributePolicy($attributePolicy, $attributes); return $attributes; } @@ -1071,6 +1077,68 @@ protected function _decryptAssertion($dom) } } + /** + * Apply attribute name mapping to extracted attributes + * + * @param array $attributeMap Associative array mapping IdP attribute names to local names + * @param array $attributes Associative array of attribute names => values + * + * @return array Attribute list containing renamed/merged attributes + */ + protected function _applyAttributeMapping($attributeMap, $attributes) + { + $mappedAttributes = array(); + + foreach ($attributes as $attributeName => $attributeValues) { + # Generate hash of new values + + # Default value: identity function + $newAttrName = $attributeName; + if (array_key_exists($attributeName, $attributeMap)) { + # Apply mapping function + $newAttrName = $attributeMap[$attributeName]; + } + + # Merge into already-mapped attribute assoc array + # (allows for multiple source attributes to be merged) + foreach ($attributeValues as $newAttrValue) { + if (!array_key_exists($newAttrName, $mappedAttributes)) { + $mappedAttributes[$newAttrName] = array(); + } + array_push($mappedAttributes[$newAttrName], $newAttrValue); + } + } + return $mappedAttributes; + } + + /** + * Filter attribute values + * + * @param array $attributePolicy Associative array of filter functions per-attribute-name + * @param array $attributes Associative array of attribute names => values + * + * @return array Attribute list containing filtered attribute values + */ + protected function _applyAttributePolicy($attributePolicy, $attributes) + { + $filteredAttributes = array(); + + foreach ($attributes as $attributeName => $attributeValues) { + # Generate hash of new values + + # Default value: identity function + $newAttrValues = $attributeValues; + if (array_key_exists($attributeName, $attributePolicy)) { + # Apply mapping function + $newAttrValues = $attributePolicy[$attributeName]($attributeValues); + } + if (count($newAttrValues) > 0) { + $filteredAttributes[$attributeName] = $newAttrValues; + } + } + return $filteredAttributes; + } + /* After execute a validation process, if fails this method returns the cause * * @return string Cause diff --git a/lib/Saml2/Settings.php b/lib/Saml2/Settings.php index 74defa72..24d02527 100644 --- a/lib/Saml2/Settings.php +++ b/lib/Saml2/Settings.php @@ -420,6 +420,12 @@ private function _addDefaultValues() if (!isset($this->_sp['privateKey'])) { $this->_sp['privateKey'] = ''; } + if (!isset($this->_sp['attributeMap'])) { + $this->_sp['attributeMap'] = array(); + } + if (!isset($this->_sp['attributePolicy'])) { + $this->_sp['attributePolicy'] = array(); + } } /** diff --git a/tests/src/OneLogin/Saml2/ResponseTest.php b/tests/src/OneLogin/Saml2/ResponseTest.php index df8e14d2..35caa128 100644 --- a/tests/src/OneLogin/Saml2/ResponseTest.php +++ b/tests/src/OneLogin/Saml2/ResponseTest.php @@ -1514,4 +1514,93 @@ public function testIsValidSignWithEmptyReferenceURI() $this->assertTrue(!empty($attributes)); $this->assertEquals('saml@user.com', $attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'][0]); } + + public function testAttributeMapping() + { + $settingsDir = TEST_ROOT .'/settings/'; + include $settingsDir.'settings1.php'; + $settingsInfo['sp']['attributeMap'] = array( + 'mail' => 'urn:oid:1.3.6.1.7', + ); + + $xml = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $response = new OneLogin_Saml2_Response($settings, $xml); + $this->assertTrue($response->isValid()); + $attributes = $response->getAttributes(); + $this->assertTrue(!empty($attributes)); + $this->assertEquals('smartin@yaco.es', $attributes['urn:oid:1.3.6.1.7'][0]); + $this->assertFalse(array_key_exists('mail', $attributes)); + } + + public function testAttributePolicy() + { + $settingsDir = TEST_ROOT .'/settings/'; + include $settingsDir.'settings1.php'; + $settingsInfo['sp']['attributePolicy'] = array( + 'eduPersonAffiliation' => function($values) + { + $validValues = array('user'); + $newValues = array(); + foreach ($values as $value) { + if (in_array($value, $validValues, true)) { + array_push($newValues, $value); + } + } + return $newValues; + }, + ); + + $xml = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $response = new OneLogin_Saml2_Response($settings, $xml); + $this->assertTrue($response->isValid()); + $attributes = $response->getAttributes(); + $this->assertTrue(!empty($attributes)); + $this->assertTrue(in_array('user', $attributes['eduPersonAffiliation'])); + $this->assertFalse(in_array('admin', $attributes['eduPersonAffiliation'])); + } + + public function testAttributeMappingAndPolicy() + { + $settingsDir = TEST_ROOT .'/settings/'; + include $settingsDir.'settings1.php'; + + $attrHelpers = new OneLogin_Saml2_Settings_AttributePolicyHelpers; + + $settingsInfo['sp']['attributeMap'] = array( + 'mail' => 'urn:oid:1.3.6.1.7', + ); + $settingsInfo['sp']['attributePolicy'] = array( + 'eduPersonAffiliation' => $attrHelpers->restrictValuesTo(array('user')), + 'urn:oid:1.3.6.1.7' => $attrHelpers->requireScope('yaco.es'), + ); + + $xml = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $response = new OneLogin_Saml2_Response($settings, $xml); + $this->assertTrue($response->isValid()); + $attributes = $response->getAttributes(); + $this->assertTrue(!empty($attributes)); + $this->assertEquals('smartin@yaco.es', $attributes['urn:oid:1.3.6.1.7'][0]); + $this->assertFalse(array_key_exists('mail', $attributes)); + $this->assertTrue(in_array('user', $attributes['eduPersonAffiliation'])); + $this->assertFalse(in_array('admin', $attributes['eduPersonAffiliation'])); + + $settingsInfo2 = $settingsInfo; + $settingsInfo2['sp']['attributePolicy'] = array( + 'eduPersonAffiliation' => $attrHelpers->restrictValuesTo(array('user')), + 'urn:oid:1.3.6.1.7' => $attrHelpers->requireScope('yaco.com'), + ); + $settings2 = new OneLogin_Saml2_Settings($settingsInfo2); + $response2 = new OneLogin_Saml2_Response($settings2, $xml); + $this->assertTrue($response2->isValid()); + $attributes2 = $response2->getAttributes(); + $this->assertTrue(!empty($attributes2)); + $this->assertFalse(array_key_exists('urn:oid:1.3.6.1.7', $attributes2)); + + } }