Skip to content

Conversation

@nathanjrobertson
Copy link
Contributor

I have a number of cases where I'm looking to use authprocs to add extra attributes, but only if certain attributes already exist, and in some cases only if they have particular values or the names or values of the attributes match given regular expressions. I've found %precondition to be ok for really simple cases, but maintenance and debugging to be a pain - I really want something more expressive and declarative.

I previously wrote PR #2559 to partially address this, but it was a bit of a hack to tack on support for just a few use cases. The incompleteness bothered me, so I've since torn that down and produced a far more comprehensive way to declaratively specify conditional adding of attributes in an authproc.

In doing this, the base case of unconditionally adding an attribute (essentially what the existing core:AttributeAdd does) remains reasonably simple:

'authproc' => [
    50 => [
        'class' => 'core:AttributeConditionalAdd',
        'attributes' => [
            'source' => ['myidp'],
        ],
    ],
],

However, the main feature of this PR is the conditions key, which has a list of optional conditionals, which if satisfied, the attributes are added. By default, if more than one condition is specified, all must pass (essentially AND). The %anycondition flag switches this to "one or more must pass" (essentially OR).

The full dictionary of supported conditionals is:

    'authproc' => [
        50 => [
            'class' => 'core:AttributeConditionalAdd',
            'conditions' => [
                'attrExistsAny' => [ // any of the attributes listed exist
                    'supplierId',
                    'custid',
                ],
                'attrExistsAll' => [ // all of the attributes listed exist
                    'staffId',
                    '',
                ],
                'attrExistsRegexAny' => [ // an attribute exists that matches one of these regexps
                    '/^.*phone.*$/',
                ],
                'attrExistsRegexAll' => [ // all of the regexps listed match at least one attribute
                    '/^.*phone.*$/',
                ],
                'attrValueIsAny' => [ // any of the attributes listed below has any of the values listed
                    'departmentName' => ['Physics', 'Chemistry'],
                ],
                'attrValueIsAll' => [ // all of the attributes listed below has all of the values listed
                    'groups' => ['users', 'admins'],
                ],
                'attrValueIsRegexAny' => [ // any of the attributes listed below has a value that matches any of the regex values listed
                    'employeeType' => ['/^staff$/'],
                ],
                'attrValueIsRegexAll' => [ // all of the attributes listed below, every value matches one of the regex values listed
                    'employeeType' => ['/^staff$/'],
                ],
            ],
            'attributes' => [
                'isInternal' => ['true'],
            ],
        ],
    ],

The %replace flag is retained from the existing core:AttributeAdd, and there is a new %nodupe flag, which removes duplicate values when appending new values to an attribute.

One final example (taken from the documentation) showing the power of what this PR brings - in the below case, the user must either have a "supplierId" attribute, or have the "staff" role and be in the "Procurement" department to receive the 'allowedSystems' => ['procurement'] attribute:

'authproc' => [
    50 => [
        'class' => 'core:AttributeConditionalAdd',
        '%anycondition',
        'conditions' => [
            'attrExistsAny' => [
                'supplierId',
            ],
            'attrValueIsAll' => [
                'role' => ['Staff'],
                'departmentName' => ['Procurement'],
            ],
        ],
        'attributes' => [
            'allowedSystems' => ['procurement'],
        ],
    ],
],

The implementation itself is quite extensible. To add an extra type of condition later you implement three private methods (eg. if you were implementing attrHasSomethingOrOther, you'd implement setAttrHasSomethingOrOther() and isConfiguredAttrHasSomethingOrOther() and processConditionalAttrHasSomethingOrOther(). So extra new types of conditionals are now trivial to add later on.

Included in this PR is 50+ PHPUnit tests and detailed user documentation which includes examples.

Follow-up work would be to have the existing core:AttributeAdd just call this, providing backward compatibility but not carrying extra code. However, I thought I'd get this reviewed first before disturbing existing functionality.

@tvdijen
Copy link
Member

tvdijen commented Dec 18, 2025

The failing PHP 8.5 tests are not your fault. We will fix this

@nathanjrobertson
Copy link
Contributor Author

The failing PHP 8.5 tests are not your fault. We will fix this

Yeah, I took a look at it yesterday. Looks like a dependency that is currently in composer.lock depends on PHP 8.2-8.4, and there is an updated version of it that works only with PHP 8.3-8.5, so I figured I'd leave that one to a true genius whilst I paddle in the shallow end of the pool.

@tvdijen
Copy link
Member

tvdijen commented Dec 18, 2025

Well, this genius decided that for SSP 2.5 we're going to bump the minimum PHP version to 8.3!
True rocket scientist here :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants