Problem/Motivation
This module purports to map SAML attributes to user fields. However, there is no mention that these mappings can only be for single-value fields. Neither in the project documentation nor the UI. I was unpleasantly surprised when I uncovered this, and I desperately need this feature.
Steps to reproduce
Map a multi-value SAML field to a multi-value user field. You will see the first value populated, but no others. There's no warning in the UI when this occurs. Turn on extended logging and you'll see a brief mention of it from modules/samlauth_user_fields/src/EventSubscriber/UserFieldsEventSubscriber.php line 565:
SAML attribute %name has multiple values; we only support using the first one: @values.
Resolution
(Text copied from CHANGELOG.md)
A new option "Synchronize all values of multi-value attributes" (`samlauth_user_fields.mappings:multiple_values`) makes user fields that can hold multiple values mirror all values of the mapped SAML attribute(s). The order of values is ignored when checking whether a field needs to be updated. The option is enabled on new installations. Existing installations keep their previous behavior until it is enabled: such a field is reduced to only the attribute's first value whenever that value changes (and left alone otherwise).
- If all attributes sent from the IdP (and used by the `samlauth_user_fields` module) are single-value, this option can safely be enabled without causing changes. The option (and its old value 'disabled') will likely be deprecated in the future.
- Custom event subscribers that have extended UserFieldsEventSubscriber should be audited for impact, before enabling this option. (E.g. if `getUpdatableAttributeValue()` was overridden, check if `getUpdatableAttributeValues()` now needs to be overridden too.)
- "Use for linking" also follows this option: when disabled / previously, the first value of an attribute would be used for matching users. When enabled, the full attribute value is used.
----
Old text for proposed resolution
Drupal Entity API supports multiple values, so there is no underlying reason why this module cannot sync multiple values. The limitation appears to be around the fact that UserFieldsEventSubscriber::getAttribute only returns one value. This fact is specifically called out in that method's comment documentation, but I'm not sure why:
* This is suitable for single-value attributes. For multi-value attributes,
* we log a debug message to make clear we're dropping data (because this
* indicates that the site owner may need to take care of getting more
* sophisticated path mapping code).
Another aspect of my confusion is why UserFieldsEventSubscriber attempts to link the user based on the mapped values. Unless I'm missing it, this is never mentioned in any documentation. Enabling samlauth_user_fields is supposed to "Maps custom SAML SSO attributes to user fields"
I see that UserFieldsEventSubscriber::getAttribute is used for both use sync and user link, and its use in user link is for building an entity query.
My proposed solution is to either build a separate getAttribute method that supports multiple values for use in user sync, or to update the existing one to support them everywhere it is used.
I would also like clarity on why samlauth_user_fields does user linking as well as syncing.
Issue fork samlauth-3522489
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
roderikMulti-value fields are easily supportable in principle, as you indicate; it just needs some thought and testing. And you are the first one to bring it up in all these years, so that thought/testing hasn't been invested yet.
The challenge for generic support is two fold:
1. On the 'source' side (the SAML message), we get all values as arrays, so there is no way to distinguish between a single value and an array containing a single value. (Feel free to set me straight, if e.g. the attribute data in your SAML responses is somehow marked as "an actual array".)
2. On the 'destination' side, not all fields actually support multi-value. (Case in point: user name, email, language.) And some may be configured/defined to be only single-value, even though the Drupal data model supports multiple. We don't want to just set a multi-value array into these fields, at least not without testing / logging a message.
You're welcome to contribute or sponsor adding this functionality. Even a only-partly-generic patch or extra subscriber helps get it into the general module in time. Failing that: it should be fairly easy to roll your own event subscriber with just a few lines of actual logic. Something like
I'm not sure whether you're talking about the boolean
map_usersconfig value (which didn't initially exist at all / had its reasons for being introduced, but I'm not sure that's relevant here). Bottom line is: linking is optional.samlauth_user_fields.field_mappingis a per-field mapping, and no field is linked unless you explicitly set the optional 'link_user_order' sub-value for the specific field.(From just reviewing the config schema, I'm not sure from the top of my head if saving the
samlauth_user_fields.field_mappingvalue always saves a 'link_user_order' with a default value of 0. It wasn't supposed to. If it does: that's a bug. If it doesn't: I don't see any issue with this.)Comment #3
pianomansam commented@roderik thanks for the extensive reply. Unfortunately, `UserFieldsEventSubscriber` has proven to be obtuse and IMHO perhaps overengineered. So I'll most likely be writing a one-off data sync using the
SamlauthEvents::USER_SYNCevent for my purposes.I'm referencing the
SamlauthEvents::USER_LINKevent withinUserFieldsEventSubscriber:Comment #4
pianomansam commentedA few more responses...
I'm not sure why that matters, if we can solve for knowing whether or not the destination of the mapping handles arrays.
This is true, but isn't it possible to know if the destination supports multiple values? If it's an entity property (like username, email, language, etc), shouldn't that be accessible from the entity definition? If it's a field on the user, shouldn't that be accessible from the field's configuration?
Comment #5
roderikThat's fair. If there are any others who have needed this multi-value functionality, they have apparently also done that.
Very possibly yes. With some hardcoding for field types probably, but that's OK. That is why I said [anyone is] welcome to contribute or sponsor adding this functionality.
So this issue stays open for that.
Despite the "obtuse and perhaps overengineered" comment, I do think it is clear enough where to add code for this.
Comment #6
roderikI've added a nots to the bottom of the project README.
Comment #7
kfolsom commented@roderik, the SAML attributes that my institution's IdP returns includes a multi-value attribute that was mapped to a multi-value user field when we used the simplesamlphp_auth module, via the simplesamlphp_custom_attributes module. One of our website maintainers who recently migrated to samlauth noticed the missing data and came upon this issue thread.
I have used Claude Code to help me develop an update for the samlauth module that successfully handles multi-value mapping. I have done a code review and testing, as well as patching a production site running samlauth 3.14, and I have found no problems with the update.
Do you accept AI-developed code in an MR, with clear indication that it was developed with the assistance of AI? If so, I will submit it and be ready to answer questions and make changes, if required. Thanks much.
Comment #8
roderik@kfolsom I am curious to see the MR. I still don't have much experience reviewing AI code, but in principle I'd like to include it. if I can understand it / vouch for it, it can go in. (Which has been the case with the few Claude-assisted MRs that I've tried to read so far.)
Final judgment comes after review. I really want a module like this one (auth related) to keep containing human readable code. On the other hand, if this is going to be a separate (perhaps separately enable-able) subscriber, it may not be a big deal.
Comment #9
kfolsom commentedCurrently, this is not a separate subscriber but modifications made to the 'UserFieldsEventSubscriber'. If you believe that enabling this for everyone is problematic, it could be made an optional feature. One of the consequences for those who already have multi-value user fields mapped to multi-value SAML attributes would be that those fields will be updated with all attribute values, replacing the single value already there.
I'll submit the MR and we can go from there. Thanks much.
Comment #11
roderikReviewed. All good. Needs Work for Q/A 1.
Added a note on AI to the project page.
Apart from that, I'm going to take a closer look at the linking. I suspect the following remark may not be true:
I'm planning to
Comment #12
roderikFWIW:
Of course it's true. (I didn't fully read back my own ~6-year-old code, which I never used / noone else commented on.)
EDIT -
Like I said, I'm going to think about how I might evolve the linking behavior in the multi-value case... if there is a logical case to be made for it. The only reason why I might bolt it onto this PR, is because of the config option that it will introduce. If the linking behavior can/should change, based on the same option, I might commit it at the same time.
But it's not your problem.
Comment #13
kfolsom commentedThanks for the thorough review, @roderik. I will work on Q/A 1 and update the MR.
Comment #14
kfolsom commentedHi, @roderik. I've updated the MR. The opt-in has been added: a per-mapping "Synchronize all values" option, on by default for new mappings and off for existing ones (no update hook needed). I also made the comparison ignore value order, since our IdP sends the same values in a different order between logins; duplicates are kept as agreed. The branch is rebased on current 8.x-3.x and I updated the expected debug message in the new UserFieldsLinkTest, because this MR rewords that message. The pipeline is green. MR description updated with details and two small questions. Setting to Needs review.
Comment #15
roderikStatus: Needs work for 1 requested code change + needs review+maybe-work for 1 proposal.
On me:
Comment #16
kfolsom commentedThanks for the feedback, @roderik. I've made the changes you suggested, described below.
Done: areInputValuesEqual() now receives the untruncated values plus the field's cardinality, and array_slice() happens only when writing. A field holding as many values as it can, all of which are among the attribute values, is now left unchanged (your [1,2,3] → [2,3] example, cardinality 2), so it no longer oscillates when the IdP reorders values. The debug log stays where it was.
Note: I read "should return FALSE" as "should not be updated", so the comparison returns TRUE ("equal") in that case. Correct me if you meant otherwise. I added an optional $cardinality parameter because the field name alone doesn't tell full from incomplete (e.g. [2,3] is complete for cardinality 2 but not 3). Added 7 test cases.
I'm leaving the option-off (single-value) behavior as is, per your comment.
Comment #17
roderikThank you. One line of feedback, but I can implement that.
I'll move the README section somewhere else, because I don't think this is "incomplete functionality" anymore.
I happen to need the new multi-value-linking code for something else... so... if I manage to finish that code with good test coverage, I'll merge this issue with that code committed to the same MR.
Comment #19
roderikThanks again for seeing this through.
Merged, so I can build on my linking code for something else (which may be included before a next release). Feedback still welcome, but I'll set Fixed.
Comment #21
kfolsom commentedThank you for your feedback on this MR and continuing support for the samlauth package.