When attempting to login with an existing user on the IdP, but new to Drupal, a new user is created without issue. When attempting to login with an existing user, where the user id exists on both the IdP and the Drupal site, I receive the following error:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'username' for key 'name': UPDATE {users} SET name=:db_update_placeholder_0 WHERE (uid = :db_condition_placeholder_0) ; Array ( [:db_update_placeholder_0] => username [:db_condition_placeholder_0] => 16555 ) in simplesamlphp_auth_user_insert() (line 326 of [path to]\sites\all\modules\simplesamlphp_auth\simplesamlphp_auth.module).
This is on a Windows box with MySQL db. Drupal 7.7, SimpleSAMLphp_auth 7.x-1.0
Comments
Comment #1
geekwisdom CreditAttribution: geekwisdom commentedThe PDOException says that simplesamlphp_auth_user_insert() is trying to update the users.name column for uid 16555 with the value "username". Do you have multiple users with the name "username"?
Let me make sure I understand your scenarios.
Scenario #1: If Joe Blogs (eduPersonPrincipalName: joe_blogs@idp.example.com; mail: joe_blogs@example.com) is a valid user at the IdP but does not exist in the Drupal site he is able to authenticate against the IdP and the simplesamlphp_auth module will properly create a corresponding Drupal user for him. Is this correct?
Are you saying that if Joe Blogs were to log out and then attempt to log in a second time he would receive this error?
Scenario #2: Or are you saying that scenario works fine (creating new accounts and letting those users come and go fine) but if a user already exists in Drupal and had previously been using a local Drupal account to log in, but now tries to access Drupal using SAML that the process will fail with this error?
Additional information that could shed some light on this issue:
Which attribute from SimpleSAMLphp are you using for the Drupal Username attribute?
Which attribute from SimpleSAMLphp are you using for the unique identifier?
Note to self:
If the issue is scenario #2 it could be that user_external_load fails to locate the user because they do not have a record in authmap. (Which would make sense if they were created before the simplesamlphp_auth module was installed.) If this is the case, then some armor plating that attempts to find a matching user with user_load_by_name() needs to be added to .module between lines 181 and 184, in simplesamlphp_auth_init().
Comment #2
kpastore CreditAttribution: kpastore commentedI was able to fix this issue, but I thought clarifying what the actual parameters were would help.
We started with a preexisting set of users on a legacy CMS (not Drupal). Those users had many profile features that needed to be ported from the old site to the new Drupal site. We used Migrate to do this. This created users in the Drupal database that associated with their profile information.
These users ALSO existed on the IdP, but not with as many fields (email, GUID and username). They were being authenticated by their GUID field on the old system.
When attempting to login with this existing user, authentication failed, while new users created on the IdP worked just fine.
To fix this issue, I had to manually add the users, with their GUID to the 'authmap' table in the Drupal db, as this step was not automatic. Once this was completed, the users authenticated correctly.
Comment #3
geekwisdom CreditAttribution: geekwisdom commentedThe issue is caused when a Drupal user that was created using the Drupal interface decides to try logging in using SAML. The module fails to find them because the user does not have a record in the authmap table. (The authmap table is used to map Drupal user accounts to external accounts.) Adding a record to the authmap table, which joins user's Drupal uid and their external identifier (authmap.authname) from the SAML IdP will solve the problem.
The question is, how can we reliably make this connection at runtime?
In Drupal no two users can have the same user.name; this is controlled both by core code and by a database constraint that requires this column to be unique.
Drupal core also forbids two users with the same user.mail, however this is not enforced at the database level and there are modules (e.g., Shared Email) that circumvent this requirement. So, comparing against e-mail seems like a bad idea.
It would seem that if the identifier received from the SAML IdP (simplesamlphp_auth_unique_id) matched the user's name (users.name) then the connection could be made by inserting a record into authmap.
If they do not match then it should be assumed that this user is a new user.
The problem with trying to match authname (from SAML assertion) with user.name in Drupal is that they will almost NEVER match in most scenarios. This is especially true if authname is configured to use eduPersonPrincipalName or eduPersonTargetedID.
So, while certain configurations could theoretically be configured in a way that could make the match, it's not likely that matches would ever be made at runtime seems. A modification that did this kind of matching is likely to be problematic, or at least limited in its usefulness.
This problem seems to call for another module, or a major new administrative feature in this one, that makes it easy for a Drupal administrator to populate the authmap table with the appropriate authnames for existing Drupal users. Perhaps such a module or feature could allow an administrator to load a CSV file containing records that link either user.name with authname or e-mail with authname. Then the code could resolve user.name or e-mail to a drupal uid and use it to create the appropriate authmap record for each user.
Comment #4
doublejosh CreditAttribution: doublejosh commentedImagine I'm about to have this same issue.
We've used Drupal to manage profiles and users for a few years, but are now moving to OpenAM and simpleSAML as an SSO solution. Drupal will just be consuming the identity for authentication. However, almost all users will have existing Drupal accounts (the scenario described as the problem).
Solution seems to be attempting to create the authmap table entry at the time of authentication if it doesn't exist rather than only at the time of provisioning.
I haven't dug in yet, so looking to clarify the problem with those who've dealt with it.
(Should mention: we'll still be on D6 for a bit longer.)
Comment #5
geekwisdom CreditAttribution: geekwisdom commentedI've clarified this issue a little more in my analysis comment above. Further comments welcome.
Comment #6
geekwisdom CreditAttribution: geekwisdom commentedkpastore, thanks for posting your resolution.
Comment #7
doublejosh CreditAttribution: doublejosh commentedThanks all for sharing your situations.
Curious why this module doesn't create authmap entries for all existing users during the install? Perhaps somewhat irresponsible :)
Maybe need a batch process from the admin interface called "Regenerate Authmap For All Users" ?
Comment #8
Lauren Kelly CreditAttribution: Lauren Kelly commentedWe worked around this with the Rules module, using a rule triggered upon creating a user.
For us, only users with a certain role are able to use SSO, so there is a condition in place, but you certainly don't need that if all users will be using SSO.
The action is custom PHP code, which adds a record to authmap with the UID, username, and module name.
This is the export of the rule :
Comment #9
doublejosh CreditAttribution: doublejosh commentedHere's a query that will insert authmaps for all users (excluding a specific role, 8 in my case) and update any existing entries (in case you previously used LDAP, etc.) Hope this is helpful to someone else out there as it was a slight pain. Placed it in the simplesamlphp_auth.install for an update. Would be better as a batch process triggered via the settings page.
Comment #10
geekwisdom CreditAttribution: geekwisdom commentedI'm changing this to a feature request because it's not really a bug and correcting it with any code that automatically makes assumptions about what an existing user's authmap entry should be could actually create a bug.
I can envision two aspects to the feature request:
1) An administrative feature that allows an administrator to do one or more of the following:
2) A couple additional configuration settings that will cause authmap entries to be created for new Drupal users (registered through Drupal). One of the new settings would need to allow the administrator to decide whether the Drupal user name or mail should be used for the authmap entry.
Would these features have helped you address the issues in your respective environments?
Comment #11
doublejosh CreditAttribution: doublejosh commentedThis would have done it.
At the time I needed to retroactively add authmap entries for everyone (other than one role) with: username | 'simplesamlphp_auth'
As you know this module does currently create authmap entries for folks registered via SSO due to user_external_login_register().
It should create entries for new users registered via Drupal under certain conditions.
I personally would do it for all users as I only have one IdP.
Comment #12
doublejosh CreditAttribution: doublejosh commentedCreated an issue for the potential problem caused by missing authmaps for native Drupal registrations...
#1635578: Path into Integrity Constraint Violation
Comment #13
doublejosh CreditAttribution: doublejosh commentedI was going to suggest a Rules integration point for external login, where I need insert an authmap, but It hink this is much simpler.
#1576460: Add Rules integration for register & login
Comment #14
doublejosh CreditAttribution: doublejosh commentedAdding a super simple admin option...
Not tested yet.
If this works out I'll create a patch.
Comment #15
doublejosh CreditAttribution: doublejosh commentedThinking I may do this via my boot submodule so I can only add authmaps to local accounts when the SSO cookie is present and they actually need it.
Comment #16
msankhala CreditAttribution: msankhala commentedsubscribe
Comment #17
colanI agree with the way forward in #10, and #14 is a reasonable place to start.
Moving to latest dev branch. Also, setting to active as there's no patch here yet.
Comment #18
LiceBaseAdmin CreditAttribution: LiceBaseAdmin commentedI would opt to put this in the bug category again, because this behavior is somewhat counterintuitive and violates reasonable assumptions about the module works. Neither from the documentation nor the help text in the configuration page could the user expect this to happen. For example the following configuration option:
"Reevaluate roles every time the user logs in.
NOTE: This means users could loose any roles that have been assigned manually in Drupal."
This implies, that if not checked, users will not loose any roles. In addition, the help text of "Register users" implies that module works well with already existing Drupal accounts.
At minimum there should be mention in the documentation, and a portable way to deal with it, before it could be made a feature request again.
The SQL in #9 seems to be MySQL specific and didn't work with postgres, I have just taken away the ON DUPLICATE ... part:
But this can only be run once.
Comment #19
LiceBaseAdmin CreditAttribution: LiceBaseAdmin commentedHi, and thanks for putting the SQL. The code is MySQL specific, so it didn't work for me at first.
The following modification worked for me on PostgreSQL 8.4 (got the idea from #9 and here http://stackoverflow.com/q/1009584):
This should move over all user which do not have a role set and are not super user.
If the existing entries shouls be left alone, replace
Also, removing
(users_roles.rid IS NULL) AND
should move all existing users over to simplesamlphp.Comment #20
cmcintosh CreditAttribution: cmcintosh commentedSo this error is still relevant in the Drupal 7 release of the module. It starts at, simplesamlphp_auth_init(), because of duplicate username issues. To reliably prevent this we need to do a quick check before running user_external_login_register(), to see if there is a user with an existing name in the system. If so then we need to create a Authmap entry to that username.
However, there may be cases where you need to do some additional checking to validate the user before you actually merge the accounts. Also, provide a solution path for those users who do not match with the local installations name for that user.
I suggest creating a new rule event, that fires when this happens as well as a module hook, that allows others to create solutions that match their needs. For now I addressed the issue this way, around line 247 is where user_external_login_register() is called, and this is where the error is triggered. To prevent this I have changed the code to the below:
The below code does not do any validation to see if the collision is valid or if the user is the same user in Drupal's database. This could be a security risk if you are not sure of all the users being in your IDP will match directly to the local drupal's information. Some basic validation that could be added is email check/or other values.
Currently I am on a time crunch but if anyone is interested in this then I can look at creating a patch and finishing up some additional handling.
Comment #21
colan@cmcintosh: A patch would be great. Thanks!
Comment #22
rgristroph CreditAttribution: rgristroph commentedI also ran into this issue, and worked around it with my own queries and code to update the authmap table. In my case it was a bit specialized because I altered the authmap table to allow for case-sensitive authnames. I will see if I can generalize my code and post a patch.
Comment #23
milesw CreditAttribution: milesw commentedHere's a patch to help this situation.
It adds a new configuration option "Enable authentication for manually created users", which is on by default.
When that option is enabled, authmaps get created on the fly when a user account of the same name already exists. (as I typed that just now I realized it might have security implications..?)
This patch also avoids registering a user when there will be a username collision.
Comment #24
milesw CreditAttribution: milesw commentedUpdating status.
Comment #25
milesw CreditAttribution: milesw commentedPatch #23 caused an exception whenever a saml-authenticated user manually created a new user account. This was because simplesamlphp_auth_user_insert() assumed the user being added was the same one that had authenticated via saml.
New patch attached.
Comment #26
milesw CreditAttribution: milesw commentedComment #27
azathot CreditAttribution: azathot commentedUpdating the patch to work with 7.x-2.x-dev.
Comment #28
steven.wichers CreditAttribution: steven.wichers commentedI have modified the way this is implemented so that modules can implement hook_simplesamlphp_auth_addauthmaps_alter() to specify an existing user to set an authmap for. The use case I had was matching SSO users based on their email attribute to existing Drupal accounts emails while the actual Drupal account username did not match the authname.
Comment #29
steven.wichers CreditAttribution: steven.wichers commentedSorry, I don't think the previous patch will actually apply cleanly. Try this one.
Comment #30
c.breschkow CreditAttribution: c.breschkow commentedPatch #29 worked for me!
Comment #31
snufkin CreditAttribution: snufkin commentedThe structure of the code has changed substantially in 3.x, so that we could get rid of the hook_init. Could you please reroll the patch against 7.x-3.x? Thanks!
Comment #32
msound CreditAttribution: msound commentedHere's a simple patch for latest 7.x-3.x.
Comment #33
msound CreditAttribution: msound commentedThe patch #32 has a problem, and does not work sometimes. The problem is $authname could be the user's name or email. Here is a new patch that works in both scenarios.
Comment #34
snufkin CreditAttribution: snufkin commentedCould you reroll it in a way so that it uses username as the authmap? We should use it consistently as per the summery outlines here: #2357879: Problems altering user after provisioning, also mail is empty in 'users'
Comment #35
msound CreditAttribution: msound commentedRerolling patch using username as authname.
Comment #36
bisonbleu CreditAttribution: bisonbleu commentedI'm just running into this issue.
Question: Can i safely update from 7.x-2.0-alpha2 to 7.x-3.x-dev and apply this patch in #35 without breaking my current SimpleSAML setup ?
Context - I'm new to SSO and simpleSAMLphp Authentication i.e. I have no experience with setting this up. I currently have some 70 Drupal users that were manually created prior to the activation of simpleSAML and that now need to be added to the authmap table to prevent the creation of a second account for each and everyone of them - which is what I'm running into right now.
Any advice and/or suggestion would be much appreciated. Thanks
Comment #37
snufkin CreditAttribution: snufkin commentedThe upgrade path has not been tested at all, so although I'd be very happy for more testers for 7.x-3.x-dev, I'd advise to upgrade very carefully and not on the production site. Alternatively you could use #29, although I am not sure it applies cleanly to 2.0-alpha2, or it needs to patch 2.x-dev.
Comment #38
bisonbleu CreditAttribution: bisonbleu commentedThanks for your quick reply @snufkin. I'll give it a try and report back. My plan:
- 2.0-alpha2 --> 2.x-dev + patch #29
- 2.0-alpha2 --> 3.x-dev + patch #35 (time permitting)
Comment #39
bisonbleu CreditAttribution: bisonbleu commentedFirst results.
- patch in #29 fails to apply to the current 7.x-2.x-dev
- but it (#29) applies cleanly to 7.x-2.0-alpha2 and fixes my issue: existing or manually created Drupal accounts for which there is a match are properly added to the authmap table (i.e. no new account is created).
Note - For my use case, the email is the unique identifier (not the username). So I changed the last line
+ $existing_user = user_load_by_name($context['authname']);
to
+ $existing_user = user_load_by_mail($context['authname']);
Comment #40
snufkin CreditAttribution: snufkin commentedBe careful, that although this approach works for now, it is likely to be broken when you migrate to 3.x. We are going to be using the drupal name property as the authmap in later versions of the module, which will break your approach.
I know that this sounds painful, but there is a good reason for it: the IdP determines which user attribute is the unique identifier. It may be the email address, but in some instances it is not. Actually in some of the high ed standards the email address is explicitly declared as _not_ unique.
Now on to Drupal. We have to use either the name, or the mail drupal user property as the authmap, the unique id for the user. If we were to store the IdP provided unique id (UID) as an email address, then we implicitly limit the IdP from using any string format as it wants, because Drupal enforces email validation on this property. This leaves us with the only free string field, the name. Only this approach covers both use cases:
- the email address is the UID, in which case we will have to map that to both user->name and user->mail.
- the name is the UID, in which case we have to map the name to user->name and we have to make up a string that passes Drupals internal validation.
We need to come up with an architecture that covers both cases. One would be to make the module more complex, allowing site admins to determine if they want to use the email as the authmap property, or the username. A simpler approach would be to force everyone using the user->name property as the authmap, but this means that site builders will have to introduce a new field to map the username onto.
Comment #41
bisonbleu CreditAttribution: bisonbleu commentedThanks for the warning @snufkin. Let me see if I understand.
The 2.x branch uses the user->mail as the unique identifier (UID) in the authmap BUT the 3.x branch changes that and instead uses the user->name as UID ?
Will there be a upgrade path from 2.x to 3.x ?
Comment #42
snufkin CreditAttribution: snufkin commentedActually no, the 2.x series uses the user->name. the 3.x changed that to user->mail in some locations (in save in particular). It will however change back to 2.x because of what I've described in my previous comment.
So for your use case it will be problematic either way, because none of the branches will use the mail property.
I really want an upgrade path in any case, but once we have switched back to using user->name there shouldn't be that many issues leftover.
Comment #43
bisonbleu CreditAttribution: bisonbleu commentedOk, got it. So using user->name for the unique identifier (UID) is the way to go.
Thanks.
Comment #44
snufkin CreditAttribution: snufkin commentedI've committed the patch that ensures that user->name is used as the UID from #2357879: Problems altering user after provisioning, also mail is empty in 'users', so now this issue can correctly rely on the name property being used as the authmap.
The patch is correctly using
authname_simplesamlphp_auth
as the key passed touser_set_authmap
so the entries in the authmap table will have the simplesamlphp_auth module name as the ones created on the fly. Existing functionality should not be impacted as in the conditional which checks for this feature to be enabled the fallback branch contains the old behaviour, therefore I'm committing this.Comment #47
svendecabooterThis still needs to be ported to the 8.x-3.x branch.
Comment #50
svendecabooter8.x-3.x now has feature parity with 7.x-3.x regarding this.
In the 8.x-3.x branch I have added some extra documentation / warning texts to this setting.
Attached is a patch for 7.x-3.x to included this description, since I think the consequences of enabling this feature should be better documented.
Comment #51
dureaghin CreditAttribution: dureaghin commented#8 Works for me. Thank you Lauren Kelly!
This is the export of the rule which adds a record to authmap after saving a new user account:
Check authmap records in MySQL database:
mysql -u root -p
mysql> use DB_NAME;
Comment #52
dureaghin CreditAttribution: dureaghin commentedThe whole my idea to create a view page with option to manage users from views page with flag button "Add authmap" and "Delete authmap" OR use vbo to batch all records. We need to use two rules with action to execute php code, first rule with event "A user has been flagged, under "Authmap" and second rule event with "A user has been unflagged, under "Authmap".
Flag export:
Views export:
Export of the rule to add record to authmap table:
Export of the rule to delete record from authmap table:
Works like a charm!