diff --git a/CHANGELOG b/CHANGELOG index 85fa933fa6b31ec3cf337230d6f81f2ed772d682..b0136f3a83aec7bfc75a307696a00bc8fc10071c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 7.12.0 (unreleased) - Add an option to automatically sign-in with an Omniauth provider - Better performance for web editor (switched from satellites to rugged) - GitLab CI service sends .gitlab-ci.yaml in each push call + - Add option to automatically link omniauth and LDAP identities v 7.11.4 - Fix missing bullets when creating lists diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c7f22b9388bb5c47d2fbdb5df47039e16b66061f..787b3ccfc5659f967a7591984636e625239795c1 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -192,6 +192,9 @@ production: &base allow_single_sign_on: false # Locks down those users until they have been cleared by the admin (default: true). block_auto_created_users: true + # Look up new users in LDAP servers. If a match is found (same uid), automatically + # link the omniauth identity with the LDAP account. (default: false) + auto_link_ldap_user: false ## Auth providers # Uncomment the following lines and fill in the data of the auth provider you want to use diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c234bd69e9ab11bedaf97144abd70547c1cb0bbf..1bd14a3a89f59e8ec4049391d387b533e74747a2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -88,6 +88,9 @@ end Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? +Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil? +Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil? +Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['providers'] ||= [] diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index ba5caed6131e901d61ac549a06aa48ad86456b2c..040d0ab34ffeeb52a90b96aea4eb916aec2c72ed 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -46,6 +46,10 @@ module Gitlab def gl_user @user ||= find_by_uid_and_provider + if auto_link_ldap_user? + @user ||= find_or_create_ldap_user + end + if signup_enabled? @user ||= build_new_user end @@ -55,8 +59,52 @@ module Gitlab protected + def find_or_create_ldap_user + return unless ldap_person + #If a corresponding person exists with same uid in a LDAP server, + #set up a Gitlab user with dual LDAP and Omniauth identities. + if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider) + #case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account. + user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) + else + #no account in Gitlab yet: create it and add the LDAP identity + user = build_new_user + user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn) + end + user + end + + def auto_link_ldap_user? + Gitlab.config.omniauth.auto_link_ldap_user + end + + def creating_linked_ldap_user? + auto_link_ldap_user? && ldap_person + end + + def ldap_person + return @ldap_person if defined?(@ldap_person) + + #looks for a corresponding person with same uid in any of the configured LDAP providers + Gitlab::LDAP::Config.providers.each do |provider| + adapter = Gitlab::LDAP::Adapter.new(provider) + @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) + break if @ldap_person #exit on first person found + end + @ldap_person #may be nil if we could not find a match + end + + def ldap_config + ldap_person && Gitlab::LDAP::Config.new(ldap_person.provider) + end + def needs_blocking? - new? && block_after_signup? + return false unless new? + if creating_linked_ldap_user? + ldap_config.block_auto_created_users + else + block_after_signup? + end end def signup_enabled? @@ -84,10 +132,16 @@ module Gitlab end def user_attributes - { + # Give preference to LDAP for sensitive information when creating a linked account + username, email = if creating_linked_ldap_user? + [ ldap_person.username, ldap_person.email.first ] + else + [ auth_hash.username, auth_hash.email ] + end + return { name: auth_hash.name, - username: ::Namespace.clean_path(auth_hash.username), - email: auth_hash.email, + username: ::Namespace.clean_path(username), + email: email, password: auth_hash.password, password_confirmation: auth_hash.password, password_automatically_set: true diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 44cdd1e4fab191ee3d8f90166c3e6274b5131e9d..2a982e8b10799fe3311ceceb8a61ee08d4795d45 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -13,6 +13,7 @@ describe Gitlab::OAuth::User do email: 'john@mail.com' } end + let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } describe :persisted? do let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } @@ -32,31 +33,94 @@ describe Gitlab::OAuth::User do let(:provider) { 'twitter' } describe 'signup' do - context "with allow_single_sign_on enabled" do - before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + shared_examples "to verify compliance with allow_single_sign_on" do + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - it "creates a user from Omniauth" do - oauth_user.save + it "creates a user from Omniauth" do + oauth_user.save - expect(gl_user).to be_valid - identity = gl_user.identities.first - expect(identity.extern_uid).to eql uid - expect(identity.provider).to eql 'twitter' + expect(gl_user).to be_valid + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'twitter' + end + end + + context "with allow_single_sign_on disabled (Default)" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: false } + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end end end - context "with allow_single_sign_on disabled (Default)" do - it "throws an error" do - expect{ oauth_user.save }.to raise_error StandardError + context "with auto_link_ldap_user disabled (default)" do + before { Gitlab.config.omniauth.stub auto_link_ldap_user: false } + include_examples "to verify compliance with allow_single_sign_on" + end + + context "with auto_link_ldap_user enabled" do + before { Gitlab.config.omniauth.stub auto_link_ldap_user: true } + + context "and a corresponding LDAP person" do + before do + ldap_user.stub(:uid) { uid } + ldap_user.stub(:username) { uid } + ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] } + ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' } + allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) + end + + context "and no account for the LDAP user" do + + it "creates a user with dual LDAP and omniauth identities" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql uid + expect(gl_user.email).to eql 'johndoe@example.com' + expect(gl_user.identities.length).to eql 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'twitter', extern_uid: uid } + ]) + end + end + + context "and LDAP user has an account already" do + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + it "adds the omniauth identity to the LDAP account" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@example.com' + expect(gl_user.identities.length).to eql 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'twitter', extern_uid: uid } + ]) + end + end + end + + context "and no corresponding LDAP person" do + before { allow(oauth_user).to receive(:ldap_person).and_return(nil) } + + include_examples "to verify compliance with allow_single_sign_on" end end + end describe 'blocking' do let(:provider) { 'twitter' } before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - context 'signup' do + context 'signup with omniauth only' do context 'dont block on create' do before { Gitlab.config.omniauth.stub block_auto_created_users: false } @@ -78,6 +142,64 @@ describe Gitlab::OAuth::User do end end + context 'signup with linked omniauth and LDAP account' do + before do + Gitlab.config.omniauth.stub auto_link_ldap_user: true + ldap_user.stub(:uid) { uid } + ldap_user.stub(:username) { uid } + ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] } + ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' } + allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) + end + + context "and no account for the LDAP user" do + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'and LDAP user has an account already' do + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end + + context 'sign-in' do before do oauth_user.save @@ -103,6 +225,26 @@ describe Gitlab::OAuth::User do expect(gl_user).not_to be_blocked end end + + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end end end end