Fixing SAML association for a Gitlab user

After I recently rebuilt my FreeIPA server, even after restoring a backup, a few different identifiers for my users got regenerated rather than restored. In particular, this results in attempts to log in to services that rely on the UUIDs being consistent to recognize the login as a new user rather than an existing user. In part, this seems like a combination of FreeIPA, Keycloak, and Gitlab.

For my setup, Keycloak and Gitlab communicate using SAML, and the configuration of the SAML details specifically state that the NameID must be a persistent ID, not an email address. So if the UUID is regenerated, the ID isn't consistent, and so it's a new user not the same old one.

Because of this, now users will get an HTTP 422 error that authentication failed because their email has been taken already:

Sign-in using foo auth failed

Sign-in failed because Email has already been taken.

...

If none of the options work, try contacting a GitLab administrator.

Retrieving the SAML ID

First up, we need to retrieve the UUID of the user being sent to Gitlab, so that we may update Gitlab's database to fix the UUID. An easy way is to pull it from the Gitlab log:

docker exec -it gitlab-omnibus bash
grep -E 'SAML\) Error saving user' /var/log/gitlab/gitlab-rails/application.log

And grab the value between 'user' and the parenthesized email after it. For my Keycloak setup, that value is a UUID that begins with a G-.

Updating Gitlab

Now that we have the user's email address and the updated SAML ID, let's launch the Rails console to update their information:

docker exec -it gitlab-omnibus bash
gitlab-rails console

So now we can run the following Ruby code to update the SAML ID saved in Gitlab's database:

# Replace these with the appropriate values from the log messages above
email = "example@example.com"
saml_id = "G-00000000-0000-0000-0000-000000000000" 

user = User.find_by_any_email(email)
ident = u.identities.select { |i| i.provider == "saml" }.first
ident.extern_uid = saml_id
ident.save!

And that's it! Now the user will be able to log in with the SAML provider again.