From 9bc4194e555af70d3e522519684bdb1855ce064d Mon Sep 17 00:00:00 2001 From: Martin van Es <martin@mrvanes.com> Date: Tue, 19 Jan 2016 15:59:21 +0100 Subject: [PATCH] Add GenerateAffiliation Authentication Processing Filter and unittest. GenerateAffiliation generates an attribute with fixed values based on (multiple) values found in a source attribute. In short: if member of group "A" create target attribute with value "student". --- .../docs/authproc_generateaffiliation.txt | 61 ++++++++ .../lib/Auth/Process/GenerateAffiliation.php | 110 ++++++++++++++ .../Auth/Process/GenerateAffiliationTest.php | 140 ++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 modules/core/docs/authproc_generateaffiliation.txt create mode 100644 modules/core/lib/Auth/Process/GenerateAffiliation.php create mode 100644 tests/modules/core/lib/Auth/Process/GenerateAffiliationTest.php diff --git a/modules/core/docs/authproc_generateaffiliation.txt b/modules/core/docs/authproc_generateaffiliation.txt new file mode 100644 index 000000000..37d7e427d --- /dev/null +++ b/modules/core/docs/authproc_generateaffiliation.txt @@ -0,0 +1,61 @@ +`core:GenerateAffiliation` +=================== + +Filter that generate an attribute to the user based on value(s) in another attribute. + +Default member attribute is memberOf, default target attribute is eduPersonAffiliation. +%replace can be used to replace member attribute with target attribute, otherwise both will exist +after processing filter. If the member attribute does not exist, nothing will be done or replaced. + + +Examples +-------- + +Add student affiliation based on LDAP groupmembership +Will add eduPersonAffiliation containing value "student" if memberOf attribute contains 'cn=student,o=some,o=organization,dc=org'. + + 'authproc' => array( + 50 => array( + 'class' => 'core:GenerateAffiliation', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + ), + ), + ), + + +Add student and employee affiliation based on LDAP groupmembership + + 'authproc' => array( + 50 => array( + 'class' => 'core:GenerateAffiliation', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + ), + 'employee' => array( + 'cn=employees,o=some,o=organization,dc=org', + ), + ), + ), + +Different memberof and target attributes, replace member attribute +Will add 'affiliation' containing 'student' and/or 'employee' depending on the values in 'groups' attribute and remove the latter. + + 'authproc' => array( + 50 => array( + 'class' => 'core:GenerateAffiliation', + '%replace', + 'attributename' => 'affiliation', + 'memberattribute' => 'groups', + 'values' => array( + 'student' => array( + 'cn=student,o=some,o=organization,dc=org', + ), + 'employee' => array( + 'cn=employees,o=some,o=organization,dc=org', + ), + ), + ), + diff --git a/modules/core/lib/Auth/Process/GenerateAffiliation.php b/modules/core/lib/Auth/Process/GenerateAffiliation.php new file mode 100644 index 000000000..ad563087f --- /dev/null +++ b/modules/core/lib/Auth/Process/GenerateAffiliation.php @@ -0,0 +1,110 @@ +<?php + +/** + * Filter to generate affiliation(s) based on groupmembership attribute + * + * @author Martin van Es, m7 + * @package simpleSAMLphp + */ +class sspmod_core_Auth_Process_GenerateAffiliation extends SimpleSAML_Auth_ProcessingFilter { + + /** + * The attributename we should assign affiliations to (target) + */ + private $attributename = 'eduPersonAffiliation'; + + /** + * The attributename we should generate affiliations from + */ + private $memberattribute = 'memberOf'; + + /** + * The required $memberattribute values and target affiliations + */ + private $values = array(); + + /** + * Wether $memberattribute should be replaced by target attribute + */ + 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; + } else { + throw new SimpleSAML_Error_Exception('Unknown flag : ' . var_export($value, TRUE)); + } + continue; + } + + // Set attributename + if ($name === 'attributename') { + $this->attributename = $value; + } + + // Set memberattribute + if ($name === 'memberattribute') { + $this->memberattribute = $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) { + assert('is_array($request)'); + assert('array_key_exists("Attributes", $request)'); + $attributes =& $request['Attributes']; + + $affiliations = array(); + + if (array_key_exists($this->memberattribute, $attributes)) { + $memberof = $attributes[$this->memberattribute]; + + if (is_array($memberof)) { + foreach ($this->values as $value => $require) { + if (count(array_intersect($require, $memberof)) > 0) { + SimpleSAML_Logger::debug('GenerateAffiliation - intersect match for ' . $value); + $affiliations[] = $value; + } + } + } + + if (count($affiliations) > 0) { + $attributes[$this->attributename] = $affiliations; + } + + if ($this->replace) { + unset($attributes[$this->memberattribute]); + } + + } else { + SimpleSAML_Logger::warning('GenerateAffiliation - memberattribute does not exist: ' . $this->memberattribute); + } + } +} + +?> \ No newline at end of file diff --git a/tests/modules/core/lib/Auth/Process/GenerateAffiliationTest.php b/tests/modules/core/lib/Auth/Process/GenerateAffiliationTest.php new file mode 100644 index 000000000..d1b17e8a4 --- /dev/null +++ b/tests/modules/core/lib/Auth/Process/GenerateAffiliationTest.php @@ -0,0 +1,140 @@ +<?php + +/** + * Test for the core:GenerateAffiliation filter. + */ +class Test_Core_Auth_Process_GenerateAffiliation 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_GenerateAffiliation($config, NULL); + $filter->process($request); + return $request; + } + + /** + * Test the most basic functionality. + */ + public function testBasic() { + $config = array( + 'values' => array( + 'target' => array( + 'source', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('source'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('target')); + } + + /** + * Test the %replace functionality. + */ + public function testReplace() { + $config = array( + '%replace', + 'values' => array( + 'target' => array( + 'source', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('source'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('eduPersonAffiliation', $attributes); + $this->assertArrayNotHasKey('memberOf', $attributes); + $this->assertEquals($attributes['eduPersonAffiliation'], array('target')); + } + + /** + * Test the different Attribute configurations. + */ + public function testAttributeConfig() { + $config = array( + 'attributename' => 'affiliation', + 'memberattribute' => 'group', + 'values' => array( + 'target' => array( + 'source', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'group' => array('source'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('affiliation', $attributes); + $this->assertEquals($attributes['affiliation'], array('target')); + } + + + /** + * Test unknown flag Exception + * + * @expectedException Exception + */ + public function testUnknownFlag() { + $config = array( + '%test', + 'values' => array( + 'target' => array( + 'source', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'memberOf' => array('source'), + ), + ); + $result = self::processFilter($config, $request); + } + + /** + * Test missing member attribute + * + */ + public function testMissingMemberAttribute() { + $config = array( + '%replace', + 'values' => array( + 'target' => array( + 'source', + ), + ), + ); + $request = array( + 'Attributes' => array( + 'test' => array('source'), + ), + ); + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('test', $attributes); + $this->assertArrayNotHasKey('eduPersonAffiliation', $attributes); + $this->assertEquals($attributes['test'], array('source')); + } +} \ No newline at end of file -- GitLab