From 0ed7c9ece1910e8d660f7b25ddf7ee71ee70a691 Mon Sep 17 00:00:00 2001 From: Nathan Robertson Date: Tue, 18 Nov 2025 15:38:41 +1100 Subject: [PATCH] Add "if_attr_exists" and "if_attr_regex_matches" options to AttributeAdd authproc --- modules/core/docs/authproc_attributeadd.md | 26 ++++++ .../core/src/Auth/Process/AttributeAdd.php | 48 ++++++++++ .../src/Auth/Process/AttributeAddTest.php | 90 +++++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/modules/core/docs/authproc_attributeadd.md b/modules/core/docs/authproc_attributeadd.md index 6a29e026d0..4dca788972 100644 --- a/modules/core/docs/authproc_attributeadd.md +++ b/modules/core/docs/authproc_attributeadd.md @@ -6,6 +6,10 @@ Filter that adds attributes to the user. If the attribute already exists, the values added will be merged into a multi-valued attribute. If you instead want to replace the existing attribute, you may add the `%replace` option. +If you want to only add the attribute if another attribute (or attributes) already exist, you can +specify the optional `%if_attr_exists` (for plain strings) or `%if_attr_regex_matches` +(for regular expressions). Both can be specified as either a single value or an array of values. + Examples -------- @@ -46,3 +50,25 @@ Replace an existing attributes: 'uid' => ['guest'], ], ], + +Add a single-valued attribute if at least one of two existing attributes exist: + + 'authproc' => [ + 50 => [ + 'class' => 'core:AttributeAdd', + '%if_attr_exists' => ['studentId', 'staffId'], + 'internalUser' => ['true'], + ], + ], + +Add a single-valued attribute if a regular expression matches an existing attribute. In this case, +if there is an existing attribute where the attribute name starts with "graduateOf", then add a +new "hasGraduated" attribute: + + 'authproc' => [ + 50 => [ + 'class' => 'core:AttributeAdd', + '%if_attr_regex_matches' => '/^graduateOf/', + 'hasGraduated' => ['true'], + ], + ], diff --git a/modules/core/src/Auth/Process/AttributeAdd.php b/modules/core/src/Auth/Process/AttributeAdd.php index 62e576df83..c74cd4ba7a 100644 --- a/modules/core/src/Auth/Process/AttributeAdd.php +++ b/modules/core/src/Auth/Process/AttributeAdd.php @@ -23,6 +23,19 @@ class AttributeAdd extends Auth\ProcessingFilter */ private bool $replace = false; + /** + * Flag which indicates only to add the new attribute if one of this list of attributes already exists. + * @var array + */ + private array $if_attr_exists = []; + + /** + * Flag which indicates only to add the new attribute if one of the regular expressions in this list + * matches one of the existing attributes. + * @var array + */ + private array $if_attr_regex_matches = []; + /** * Attributes which should be added/appended. * @@ -50,6 +63,21 @@ public function __construct(array &$config, $reserved) throw new Exception('Unknown flag: ' . var_export($values, true)); } continue; + } elseif (str_starts_with($name, "%")) { + if ($name === '%if_attr_exists') { + if (!is_array($values)) { + $values = [$values]; + } + $this->if_attr_exists = $values; + } elseif ($name === '%if_attr_regex_matches') { + if (!is_array($values)) { + $values = [$values]; + } + $this->if_attr_regex_matches = $values; + } else { + throw new Exception('Unknown option: ' . var_export($name, true)); + } + continue; } if (!is_array($values)) { @@ -81,6 +109,26 @@ public function process(array &$state): void $attributes = &$state['Attributes']; + $shouldAdd = empty($this->if_attr_exists) && empty($this->if_attr_regex_matches); + foreach ($this->if_attr_exists as $attrName) { + if (array_key_exists($attrName, $attributes)) { + $shouldAdd = true; + break; + } + } + foreach ($this->if_attr_regex_matches as $regex) { + foreach (array_keys($attributes) as $attrName) { + if (preg_match($regex, $attrName) === 1) { + $shouldAdd = true; + break 2; + } + } + } + + if (!$shouldAdd) { + return; + } + foreach ($this->attributes as $name => $values) { if ($this->replace === true || !array_key_exists($name, $attributes)) { $attributes[$name] = $values; diff --git a/tests/modules/core/src/Auth/Process/AttributeAddTest.php b/tests/modules/core/src/Auth/Process/AttributeAddTest.php index 77a563bcc4..39cd33de45 100644 --- a/tests/modules/core/src/Auth/Process/AttributeAddTest.php +++ b/tests/modules/core/src/Auth/Process/AttributeAddTest.php @@ -187,4 +187,94 @@ public function testWrongAttributeValue(): void ]; self::processFilter($config, $request); } + + + /** + * Test the most basic functionality. + */ + public function testBasicAttrExists(): void + { + $config = [ + '%if_attr_exists' => ['memberOf'], + 'test' => ['value1', 'value2'], + ]; + $request = [ + 'Attributes' => [ + 'memberOf' => ['theGroup'], + ], + ]; + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('test', $attributes); + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertEquals($attributes['test'], ['value1', 'value2']); + $this->assertEquals($attributes['memberOf'], ['theGroup']); + } + + + /** + * Test the most basic functionality. + */ + public function testBasicAttrExistsFail(): void + { + $config = [ + '%if_attr_exists' => ['someOtherAttr'], + 'test' => ['value1', 'value2'], + ]; + $request = [ + 'Attributes' => [ + 'memberOf' => ['theGroup'], + ], + ]; + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayNotHasKey('test', $attributes); + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertEquals($attributes['memberOf'], ['theGroup']); + } + + + /** + * Test the most basic functionality. + */ + public function testBasicRegexMatches(): void + { + $config = [ + '%if_attr_regex_matches' => ['/^member/'], + 'test' => ['value1', 'value2'], + ]; + $request = [ + 'Attributes' => [ + 'memberOf' => ['theGroup'], + ], + ]; + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('test', $attributes); + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertEquals($attributes['test'], ['value1', 'value2']); + $this->assertEquals($attributes['memberOf'], ['theGroup']); + } + + + /** + * Test the most basic functionality. + */ + public function testBasicRegexMatchesFail(): void + { + $config = [ + '%if_attr_regex_matches' => ['/^someOther/'], + 'test' => ['value1', 'value2'], + ]; + $request = [ + 'Attributes' => [ + 'memberOf' => ['theGroup'], + ], + ]; + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayNotHasKey('test', $attributes); + $this->assertArrayHasKey('memberOf', $attributes); + $this->assertEquals($attributes['memberOf'], ['theGroup']); + } }