From 6489292dd61cbd4d4f636d525fa8442fb7f7fb49 Mon Sep 17 00:00:00 2001 From: Thomas Mendoza Date: Mon, 6 Jun 2022 22:43:44 -0700 Subject: [PATCH] Allow omniauth username claim to be configurable Adds the ability to specify an alternate auth_hash claim to use as the GitLab username Changelog: added https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89379 --- doc/integration/omniauth.md | 59 +++++++++++++++++++ lib/gitlab/auth/o_auth/auth_hash.rb | 35 ++++++++++- spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb | 30 ++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 9ea6c6146876a1..1c398ad6a8e898 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -107,6 +107,65 @@ To change these settings: After configuring these settings, you can configure your chosen [provider](#supported-providers). +### Per-provider configuration + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89379) in GitLab 15.3. + +If `allow_single_sign_on` is set, GitLab uses one of the following fields returned in the OmniAuth `auth_hash` to establish a username in GitLab for the user signing in, +choosing the first that exists: + +- `username`. +- `nickname`. +- `email`. + +You can create GitLab configuration on a per-provider basis, which is supplied to the [provider](#supported-providers) using `args`. If you set the `gitlab_username_claim` +variable in `args` for a provider, you can select another claim to use for the GitLab username. The chosen claim must be unique to avoid collisions. + +- **For Omnibus installations** + + ```ruby + gitlab_rails['omniauth_providers'] = [ + + # The generic pattern for configuring a provider with name PROVIDER_NAME + + gitlab_rails['omniauth_providers'] = { + name: "PROVIDER_NAME" + ... + args: { gitlab_username_claim: 'sub' } # For users signing in with the provider you configure, the GitLab username will be set to the "sub" received from the provider + }, + + # Here are examples using GitHub and Crowd + + gitlab_rails['omniauth_providers'] = { + name: "github" + ... + args: { gitlab_username_claim: 'name' } # For users signing in with GitHub, the GitLab username will be set to the "name" received from GitHub + }, + { + name: "crowd" + ... + args: { gitlab_username_claim: 'uid' } # For users signing in with Crowd, the GitLab username will be set to the "uid" received from Crowd + }, + ] + ``` + +- **For installations from source** + + ```yaml + - { name: 'PROVIDER_NAME', + ... + args: { gitlab_username_claim: 'sub' } + } + - { name: 'github', + ... + args: { gitlab_username_claim: 'name' } + } + - { name: 'crowd', + ... + args: { gitlab_username_claim: 'uid' } + } + ``` + ### Passwords for users created via OmniAuth The [Generated passwords for users created through integrated authentication](../security/passwords_for_integrated_authentication_methods.md) diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb index a45778159c7324..37f92792d2dd7f 100644 --- a/lib/gitlab/auth/o_auth/auth_hash.rb +++ b/lib/gitlab/auth/o_auth/auth_hash.rb @@ -59,14 +59,43 @@ def info auth_hash['info'] end - def get_info(key) - value = info[key] + def coerce_utf8(value) value.is_a?(String) ? Gitlab::Utils.force_utf8(value) : value end + def get_info(key) + coerce_utf8(info[key]) + end + + def provider_config + Gitlab::Auth::OAuth::Provider.config_for(@provider) || {} + end + + def provider_args + @provider_args ||= provider_config['args'].presence || {} + end + + def get_from_auth_hash_or_info(key) + coerce_utf8(auth_hash[key]) || get_info(key) + end + + # Allow for configuring a custom username claim per provider from + # the auth hash or use the canonical username or nickname fields + def gitlab_username_claim + provider_args.dig('gitlab_username_claim')&.to_sym + end + + def username_claims + [gitlab_username_claim, :username, :nickname].compact + end + + def get_username + username_claims.map { |claim| get_from_auth_hash_or_info(claim) }.find { |name| name.presence } + end + def username_and_email @username_and_email ||= begin - username = get_info(:username).presence || get_info(:nickname).presence + username = get_username email = get_info(:email).presence username ||= generate_username(email) if email diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb index 69068883096b7b..a044094179c688 100644 --- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb @@ -14,6 +14,7 @@ ) end + let(:provider_config) { { 'args' => { 'gitlab_username_claim' => 'first_name' } } } let(:uid_raw) do +"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net" end @@ -35,6 +36,7 @@ let(:email_utf8) { email_ascii.force_encoding(Encoding::UTF_8) } let(:nickname_utf8) { nickname_ascii.force_encoding(Encoding::UTF_8) } let(:name_utf8) { name_ascii.force_encoding(Encoding::UTF_8) } + let(:first_name_utf8) { first_name_ascii.force_encoding(Encoding::UTF_8) } let(:info_hash) do { @@ -91,6 +93,34 @@ end end + context 'custom username field provided' do + before do + allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for).and_return(provider_config) + end + + it 'uses the custom field for the username' do + expect(auth_hash.username).to eql first_name_utf8 + end + + it 'uses the default claim for the username when the custom claim is not found' do + provider_config['args']['gitlab_username_claim'] = 'nonexistent' + + expect(auth_hash.username).to eql nickname_utf8 + end + + it 'uses the default claim for the username when the custom claim is empty' do + info_hash[:first_name] = '' + + expect(auth_hash.username).to eql nickname_utf8 + end + + it 'uses the default claim for the username when the custom claim is nil' do + info_hash[:first_name] = nil + + expect(auth_hash.username).to eql nickname_utf8 + end + end + context 'auth_hash constructed with ASCII-8BIT encoding' do it 'forces utf8 encoding on uid' do expect(auth_hash.uid.encoding).to eql Encoding::UTF_8 -- GitLab