diff options
-rw-r--r-- | modules/core/docs/authproc_attributevaluemap.txt | 83 | ||||
-rw-r--r-- | modules/core/lib/Auth/Process/AttributeValueMap.php | 120 | ||||
-rw-r--r-- | tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php | 196 |
3 files changed, 399 insertions, 0 deletions
diff --git a/modules/core/docs/authproc_attributevaluemap.txt b/modules/core/docs/authproc_attributevaluemap.txt new file mode 100644 index 0000000..cf7468a --- /dev/null +++ b/modules/core/docs/authproc_attributevaluemap.txt @@ -0,0 +1,83 @@ +`core:AttributeValueMap` +=================== + +Filter that creates a target attribute based on one or more value(s) in source attribute. + +%replace can be used to replace all existing values in target with new ones (any existing values will be lost) +%keep can be used to keep the source attribute, otherwise it will be removed. + +Examples +-------- + +Add student affiliation based on LDAP groupmembership. +Will add eduPersonAffiliation containing value "student" if memberOf attribute contains +either 'cn=student,o=some,o=organization,dc=org' or 'cn=student,o=other,o=organization,dc=org'. +'memberOf' attribute will be removed (use %keep, to keep it) and existing values in +'eduPersonAffiliation' will be merged (use %replace to replace them). + + 'authproc' => array( + 50 => array( + 'class' => 'core:AttributeValueMap', + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + 'cn=student,o=other,o=organization,dc=org', + ), + ), + ), + ) + +Multiple assignments. +Add student, employee and both affiliation based on LDAP groupmembership in memberOf attribute. + + 'authproc' => array( + 50 => array( + 'class' => 'core:AttributeValueMap', + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + 'cn=student,o=other,o=organization,dc=org', + ), + 'employee' => array( + 'cn=employees,o=some,o=organization,dc=org', + 'cn=employee,o=other,o=organization,dc=org', + 'cn=workers,o=any,o=organization,dc=org', + ), + 'both' => array( + 'cn=student,o=some,o=organization,dc=org', + 'cn=student,o=other,o=organization,dc=org', + 'cn=employees,o=some,o=organization,dc=org', + 'cn=employee,o=other,o=organization,dc=org', + 'cn=workers,o=any,o=organization,dc=org', + ), + ), + ), + ) + +Replace and Keep. +Replace any existing 'affiliation' attribute values and keep 'groups' attribute. + + 'authproc' => array( + 50 => array( + 'class' => 'core:AttributeValueMap', + 'sourceattribute' => 'groups', + 'targetattribute' => 'affiliation', + '%replace', + '%keep', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + 'cn=student,o=other,o=organization,dc=org', + ), + 'employee' => array( + 'cn=employees,o=some,o=organization,dc=org', + 'cn=employee,o=other,o=organization,dc=org', + 'cn=workers,o=any,o=organization,dc=org', + ), + ), + ), + ) diff --git a/modules/core/lib/Auth/Process/AttributeValueMap.php b/modules/core/lib/Auth/Process/AttributeValueMap.php new file mode 100644 index 0000000..231f2d3 --- /dev/null +++ b/modules/core/lib/Auth/Process/AttributeValueMap.php @@ -0,0 +1,120 @@ +<?php + +/** + * Filter to create target attribute based on value(s) in source attribute + * + * @author Martin van Es, m7 + * @package simpleSAMLphp + */ +class sspmod_core_Auth_Process_AttributeValueMap extends SimpleSAML_Auth_ProcessingFilter { + + /** + * The attributename we should assign values to (ie target) + */ + private $targetattribute; + + /** + * The attributename we should create values from + */ + private $sourceattribute; + + /** + * The required $sourceattribute values and target affiliations + */ + private $values = array(); + + /** + * Wether $sourceattribute should be kept + */ + private $keep = false; + + /** + * Wether $target attribute values should be replaced by new values + */ + private $replace = false; + + /** + * 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)'); + + /* Validate configuration. */ + foreach ($config as $name => $value) { + if (is_int($name)) { + // check if this is an option + if ($value === '%replace') { + $this->replace = true; + } elseif ($value === '%keep') { + $this->keep = true; + } else { + throw new SimpleSAML_Error_Exception('Unknown flag : ' . var_export($value, true)); + } + continue; + } + + // Set targetattribute + if ($name === 'targetattribute') { + $this->targetattribute = $value; + } + + // Set sourceattribute + if ($name === 'sourceattribute') { + $this->sourceattribute = $value; + } + + // Set values + if ($name === 'values') { + $this->values = $value; + } + } + } + + + /** + * Apply filter to add groups attribute. + * + * @param array &$request The current request + */ + public function process(&$request) { + SimpleSAML_Logger::debug('AttributeValueMap - process'); + + assert('is_array($request)'); + assert('array_key_exists("Attributes", $request)'); + $attributes =& $request['Attributes']; + + // Make sure sourceattribute exists + assert('array_key_exists($this->sourceattribute, $attributes)'); + // Make sure the targetattribute is set + assert('is_string($this->targetattribute)'); + + $sourceattribute = $attributes[$this->sourceattribute]; + $targetvalues = array(); + + if (is_array($sourceattribute)) { + foreach ($this->values as $value => $require) { + if (count(array_intersect($require, $sourceattribute)) > 0) { + SimpleSAML_Logger::debug('AttributeValueMap - intersect match for ' . $value); + $targetvalues[] = $value; + } + } + } + + if (count($targetvalues) > 0) { + if ($this->replace or !@is_array($attributes[$this->targetattribute])) { + $attributes[$this->targetattribute] = $targetvalues; + } else { + $attributes[$this->targetattribute] = array_unique(array_merge($attributes[$this->targetattribute], $targetvalues)); + } + } + + if (!$this->keep) { + unset($attributes[$this->sourceattribute]); + } + } +} diff --git a/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php b/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php new file mode 100644 index 0000000..426f2ba --- /dev/null +++ b/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php @@ -0,0 +1,196 @@ +<?php + +/** + * Test for the core:AttributeValueMap filter. + */ +class Test_Core_Auth_Process_AttributeValueMap extends PHPUnit_Framework_TestCase +{ + + /** + * Helper function to run the filter with a given configuration. + * + * @param array $config The filter configuration. + * @param array $request The request state. + * @return array The state array after processing. + */ + private static function processFilter(array $config, array $request) { + $filter = new sspmod_core_Auth_Process_AttributeValueMap($config, null); + $filter->process($request); + return $request; + } + + /** + * Test the most basic functionality. + */ + public function testBasic() { + $config = array( + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + 'values' => array( + 'member' => array( + 'theGroup', + 'otherGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayNotHasKey('memberOf', $attributes); + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('member')); + } + + /** + * Test basic functionality, remove duplicates + */ + public function testNoDuplicates() { + $config = array( + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + 'values' => array( + 'member' => array( + 'theGroup', + 'otherGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup', 'otherGroup'), + 'eduPersonAffiliation' => array('member', 'someValue'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayNotHasKey('memberOf', $attributes); + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('member', 'someValue')); + } + + /** + * Test the %replace functionality. + */ + public function testReplace() { + $config = array( + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + '%replace', + 'values' => array( + 'member' => array( + 'theGroup', + 'otherGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + 'eduPersonAffiliation' => array('someValue'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayNotHasKey('memberOf', $attributes); + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('member')); + } + + /** + * Test the %keep functionality. + */ + public function testKeep() { + $config = array( + 'sourceattribute' => 'memberOf', + 'targetattribute' => 'eduPersonAffiliation', + '%keep', + 'values' => array( + 'member' => array( + 'theGroup', + 'otherGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + 'eduPersonAffiliation' => array('someValue'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('someValue','member')); + } + + /** + * Test unknown flag Exception + * + * @expectedException Exception + */ + public function testUnknownFlag() { + $config = array( + '%test', + 'values' => array( + 'member' => array( + 'theGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + ), + ); + $result = self::processFilter($config, $request); + } + + /** + * Test missing Source attribute + * + * @expectedException Exception + */ + public function testMissingSourceAttribute() { + $config = array( + 'targetattribute' => 'affiliation', + 'values' => array( + 'member' => array( + 'theGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + ), + ); + $result = self::processFilter($config, $request); + } + + /** + * Test missing Target attribute + * + * @expectedException Exception + */ + public function testMissingTargetAttribute() { + $config = array( + 'sourceattribute' => 'memberOf', + 'values' => array( + 'member' => array( + 'theGroup', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('theGroup'), + ), + ); + $result = self::processFilter($config, $request); + } +} |