Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for renaming SAML attributes and filtering attribute values #208

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ae85541
Add support for attribute name mapping and value filtering
rb12345 Apr 26, 2017
2ad298b
Add tests for new attribute mapping and filtering functionality
rb12345 Apr 26, 2017
9a6e9ed
Fix code formatting issues found by Travis tests
rb12345 Apr 26, 2017
ab96e63
Fix coveralls configuration
rb12345 Apr 26, 2017
cd63164
Fix typo caused by previous Travis fixes
rb12345 Apr 26, 2017
9f020ca
Fix more typos found by Travis
rb12345 Apr 26, 2017
6939a7b
Saml2/Response: Move applyAttribute* functions higher up the file and…
rb12345 Apr 26, 2017
cac9f47
Saml2/ResponseTest: Fix deliberate breakage of the testAttributeMappi…
rb12345 Apr 26, 2017
9807749
lib/Saml2/Response: Add documentation to _applyAttribute*() functions
rb12345 Apr 26, 2017
177c3f6
lib/Saml2/Response: Filter out attributes with no valid values entirely
rb12345 Apr 26, 2017
6c5278c
ResponseTest: Add combined attribute mapping/filtering test
rb12345 Apr 26, 2017
e8cb2b0
Travis: Attempt to cache composer cache directory across tests
rb12345 Apr 26, 2017
bdc0e80
lib/Saml2/AttributePolicyHelpers: Remove stray semi-colon
rb12345 Apr 26, 2017
0c32981
ResponseTest: Add missing semicolon to testAttributeMappingAndPolicy()
rb12345 Apr 26, 2017
d2b4051
ResponseTest: Fix typo in testAttributeMappingAndPolicy()
rb12345 Apr 26, 2017
16142a6
lib/Saml2/AttributePolicyHelpers: Fix code formatting
rb12345 Apr 26, 2017
a8506c6
ResponseTest: Fix previous typo in testAttributeMappingAndPolicy()
rb12345 Apr 26, 2017
e5f63e0
ResponseTest: Fix test so that second set of filtered attributes are …
rb12345 Apr 26, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
language: php

cache:
directories:
- $HOME/.composer/cache

php:
- 5.6
- 5.5
Expand Down
31 changes: 31 additions & 0 deletions lib/Saml2/AttributePolicyHelpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* Attribute Policy helper functions
*
*/

class OneLogin_Saml2_Settings_AttributePolicyHelpers
{
static function restrictValuesTo($validValues)
{
return function($values) use ($validValues) {
$newValues = array();
foreach ($values as $value) {
if (in_array($value, $validValues, true)) {
array_push($newValues, $value);
}
}
return $newValues;
};
}

static function requireScope($scope)
{
$scope = str_replace('.', '\.', $scope);
return function ($values) use ($scope) {
$newValues = preg_grep('/^[^@]+@' . $scope . '$/', $values);
return $newValues;
};
}
}
68 changes: 68 additions & 0 deletions lib/Saml2/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,12 @@ public function getAttributes()

$attributes[$attributeName] = $attributeValues;
}

$spData = $this->_settings->getSPData();
$attributeMap = $spData['attributeMap'];
$attributePolicy = $spData['attributePolicy'];
$attributes = $this->_applyAttributeMapping($attributeMap, $attributes);
$attributes = $this->_applyAttributePolicy($attributePolicy, $attributes);
return $attributes;
}

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/Saml2/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

/**
Expand Down
89 changes: 89 additions & 0 deletions tests/src/OneLogin/Saml2/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1514,4 +1514,93 @@ public function testIsValidSignWithEmptyReferenceURI()
$this->assertTrue(!empty($attributes));
$this->assertEquals('[email protected]', $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('[email protected]', $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('[email protected]', $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));

}
}