diff --git a/modules/core/docs/authproc_attributealter.md b/modules/core/docs/authproc_attributealter.md index 84d3d6e06239e7d65e576dad4ca44ba98966f8aa..53fe5744a3e09d925db579b5696fa27ba5518263 100644 --- a/modules/core/docs/authproc_attributealter.md +++ b/modules/core/docs/authproc_attributealter.md @@ -38,6 +38,11 @@ Parameters : Indicates that the whole value of the attribute should be removed completely if there is a match. If no other values exist, the attribute will be removed completely. This parameter is OPTIONAL. + +`%merge` +: Indicates whether the altered values must be merged with the target attribute values. The default + behaviour is to overwrite the target attribute completely. + This parameter is OPTIONAL. Examples -------- diff --git a/modules/core/lib/Auth/Process/AttributeAlter.php b/modules/core/lib/Auth/Process/AttributeAlter.php index e6db082d689f69639292666a4b0e367c33b7fc6e..6b851d7a0796a22035460e9619ca0c175f0073d0 100644 --- a/modules/core/lib/Auth/Process/AttributeAlter.php +++ b/modules/core/lib/Auth/Process/AttributeAlter.php @@ -53,6 +53,12 @@ class AttributeAlter extends Auth\ProcessingFilter */ private string $target = ''; + /** + * Should the altered value be merged with target values + * @var bool + */ + private bool $merge = false; + /** * Initialize this filter. @@ -73,6 +79,8 @@ class AttributeAlter extends Auth\ProcessingFilter $this->replace = true; } elseif ($value === '%remove') { $this->remove = true; + } elseif ($value === '%merge') { + $this->merge = true; } else { throw new Error\Exception('Unknown flag : ' . var_export($value, true)); } @@ -155,6 +163,11 @@ class AttributeAlter extends Auth\ProcessingFilter if ($this->subject === $this->target) { $value = $new_value; + } else if ($this->merge === true) { + $attributes[$this->target] = array_values( + array_diff($attributes[$this->target], [$value]) + ); + $attributes[$this->target][] = $new_value; } else { $attributes[$this->target] = [$new_value]; } @@ -183,8 +196,7 @@ class AttributeAlter extends Auth\ProcessingFilter $attributes[$this->subject] ); } else { - /** @psalm-suppress InvalidArgument */ - $attributes[$this->target] = array_diff( + $diff = array_diff( preg_replace( $this->pattern, $this->replacement, @@ -192,6 +204,14 @@ class AttributeAlter extends Auth\ProcessingFilter ), $attributes[$this->subject] ); + + if ($this->merge === true) { + /** @psalm-suppress InvalidArgument */ + $attributes[$this->target] = array_merge($diff, $attributes[$this->target] ?? []); + } else { + /** @psalm-suppress InvalidArgument */ + $attributes[$this->target] = $diff; + } } } } diff --git a/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php b/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php index 5227e0f6752901a757e60d1e6123b2785d15261e..9652c22ae035cce473a3eefb3765be00d35947bd 100644 --- a/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php +++ b/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php @@ -81,6 +81,34 @@ class AttributeAlterTest extends TestCase } + /** + * Test the most basic functionality with merging strategy. + */ + public function testMergeWithTarget(): void + { + $config = [ + 'subject' => 'test', + 'target' => 'test2', + 'pattern' => '/wrong/', + 'replacement' => 'right', + '%merge' + ]; + + $request = [ + 'Attributes' => [ + 'test' => ['wrong'], + 'test2' => ['somethingelse'], + ], + ]; + + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertArrayHasKey('test2', $attributes); + $this->assertEquals($attributes['test'], ['wrong']); + $this->assertEquals($attributes['test2'], ['right', 'somethingelse']); + } + + /** * Module is a no op if subject attribute is not present. */ @@ -155,6 +183,31 @@ class AttributeAlterTest extends TestCase } + /** + * Test replacing attribute value with merging strategy. + */ + public function testReplaceMergeMatchWithTarget(): void + { + $config = [ + 'subject' => 'source', + 'pattern' => '/wrong/', + 'replacement' => 'right', + 'target' => 'test', + '%replace', + '%merge', + ]; + $request = [ + 'Attributes' => [ + 'source' => ['wrong'], + 'test' => ['wrong', 'somethingelse'], + ], + ]; + $result = self::processFilter($config, $request); + $attributes = $result['Attributes']; + $this->assertEquals($attributes['test'], ['somethingelse', 'right']); + } + + /** * Test replacing attribute values. */