From 1ad5df49b1925f1865e99c3fd8576a762aea9cae Mon Sep 17 00:00:00 2001 From: Horatiu Eugen Vlad Date: Fri, 23 Feb 2018 13:10:39 +0100 Subject: Moved o_auth/saml/ldap modules under gitlab/auth --- spec/lib/gitlab/auth/ldap/access_spec.rb | 166 +++++ spec/lib/gitlab/auth/ldap/adapter_spec.rb | 144 +++++ spec/lib/gitlab/auth/ldap/auth_hash_spec.rb | 110 ++++ spec/lib/gitlab/auth/ldap/authentication_spec.rb | 58 ++ spec/lib/gitlab/auth/ldap/config_spec.rb | 373 ++++++++++++ spec/lib/gitlab/auth/ldap/dn_spec.rb | 224 +++++++ spec/lib/gitlab/auth/ldap/person_spec.rb | 160 +++++ spec/lib/gitlab/auth/ldap/user_spec.rb | 241 ++++++++ spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb | 111 ++++ spec/lib/gitlab/auth/o_auth/provider_spec.rb | 42 ++ spec/lib/gitlab/auth/o_auth/user_spec.rb | 742 +++++++++++++++++++++++ spec/lib/gitlab/auth/saml/auth_hash_spec.rb | 40 ++ spec/lib/gitlab/auth/saml/user_spec.rb | 403 ++++++++++++ spec/lib/gitlab/auth_spec.rb | 8 +- spec/lib/gitlab/ldap/access_spec.rb | 166 ----- spec/lib/gitlab/ldap/adapter_spec.rb | 144 ----- spec/lib/gitlab/ldap/auth_hash_spec.rb | 110 ---- spec/lib/gitlab/ldap/authentication_spec.rb | 58 -- spec/lib/gitlab/ldap/config_spec.rb | 373 ------------ spec/lib/gitlab/ldap/dn_spec.rb | 224 ------- spec/lib/gitlab/ldap/person_spec.rb | 160 ----- spec/lib/gitlab/ldap/user_spec.rb | 241 -------- spec/lib/gitlab/o_auth/auth_hash_spec.rb | 111 ---- spec/lib/gitlab/o_auth/provider_spec.rb | 42 -- spec/lib/gitlab/o_auth/user_spec.rb | 742 ----------------------- spec/lib/gitlab/saml/auth_hash_spec.rb | 40 -- spec/lib/gitlab/saml/user_spec.rb | 403 ------------ 27 files changed, 2818 insertions(+), 2818 deletions(-) create mode 100644 spec/lib/gitlab/auth/ldap/access_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/adapter_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/auth_hash_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/authentication_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/config_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/dn_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/person_spec.rb create mode 100644 spec/lib/gitlab/auth/ldap/user_spec.rb create mode 100644 spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb create mode 100644 spec/lib/gitlab/auth/o_auth/provider_spec.rb create mode 100644 spec/lib/gitlab/auth/o_auth/user_spec.rb create mode 100644 spec/lib/gitlab/auth/saml/auth_hash_spec.rb create mode 100644 spec/lib/gitlab/auth/saml/user_spec.rb delete mode 100644 spec/lib/gitlab/ldap/access_spec.rb delete mode 100644 spec/lib/gitlab/ldap/adapter_spec.rb delete mode 100644 spec/lib/gitlab/ldap/auth_hash_spec.rb delete mode 100644 spec/lib/gitlab/ldap/authentication_spec.rb delete mode 100644 spec/lib/gitlab/ldap/config_spec.rb delete mode 100644 spec/lib/gitlab/ldap/dn_spec.rb delete mode 100644 spec/lib/gitlab/ldap/person_spec.rb delete mode 100644 spec/lib/gitlab/ldap/user_spec.rb delete mode 100644 spec/lib/gitlab/o_auth/auth_hash_spec.rb delete mode 100644 spec/lib/gitlab/o_auth/provider_spec.rb delete mode 100644 spec/lib/gitlab/o_auth/user_spec.rb delete mode 100644 spec/lib/gitlab/saml/auth_hash_spec.rb delete mode 100644 spec/lib/gitlab/saml/user_spec.rb (limited to 'spec/lib/gitlab') diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb new file mode 100644 index 00000000000..9b3916bf9e3 --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/access_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::Access do + let(:access) { described_class.new user } + let(:user) { create(:omniauth_user) } + + describe '.allowed?' do + it 'updates the users `last_credential_check_at' do + expect(access).to receive(:allowed?) { true } + expect(described_class).to receive(:open).and_yield(access) + + expect { described_class.allowed?(user) } + .to change { user.last_credential_check_at } + end + end + + describe '#allowed?' do + subject { access.allowed? } + + context 'when the user cannot be found' do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + end + + it { is_expected.to be_falsey } + + it 'blocks user in GitLab' do + expect(access).to receive(:block_user).with(user, 'does not exist anymore') + + access.allowed? + end + end + + context 'when the user is found' do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) + end + + context 'and the user is disabled via active directory' do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true) + end + + it { is_expected.to be_falsey } + + it 'blocks user in GitLab' do + expect(access).to receive(:block_user).with(user, 'is disabled in Active Directory') + + access.allowed? + end + end + + context 'and has no disabled flag in active diretory' do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) + end + + it { is_expected.to be_truthy } + + context 'when auto-created users are blocked' do + before do + user.block + end + + it 'does not unblock user in GitLab' do + expect(access).not_to receive(:unblock_user) + + access.allowed? + + expect(user).to be_blocked + expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic + end + end + + context 'when auto-created users are not blocked' do + before do + user.ldap_block + end + + it 'unblocks user in GitLab' do + expect(access).to receive(:unblock_user).with(user, 'is not disabled anymore') + + access.allowed? + end + end + end + + context 'without ActiveDirectory enabled' do + before do + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:active_directory).and_return(false) + end + + it { is_expected.to be_truthy } + + context 'when user cannot be found' do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + end + + it { is_expected.to be_falsey } + + it 'blocks user in GitLab' do + expect(access).to receive(:block_user).with(user, 'does not exist anymore') + + access.allowed? + end + end + + context 'when user was previously ldap_blocked' do + before do + user.ldap_block + end + + it 'unblocks the user if it exists' do + expect(access).to receive(:unblock_user).with(user, 'is available again') + + access.allowed? + end + end + end + end + end + + describe '#block_user' do + before do + user.activate + allow(Gitlab::AppLogger).to receive(:info) + + access.block_user user, 'reason' + end + + it 'blocks the user' do + expect(user).to be_blocked + expect(user).to be_ldap_blocked + end + + it 'logs the reason' do + expect(Gitlab::AppLogger).to have_received(:info).with( + "LDAP account \"123456\" reason, " \ + "blocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + end + end + + describe '#unblock_user' do + before do + user.ldap_block + allow(Gitlab::AppLogger).to receive(:info) + + access.unblock_user user, 'reason' + end + + it 'activates the user' do + expect(user).not_to be_blocked + expect(user).not_to be_ldap_blocked + end + + it 'logs the reason' do + Gitlab::AppLogger.info( + "LDAP account \"123456\" reason, " \ + "unblocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + end + end +end diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb new file mode 100644 index 00000000000..10c60d792bd --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb @@ -0,0 +1,144 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::Adapter do + include LdapHelpers + + let(:ldap) { double(:ldap) } + let(:adapter) { ldap_adapter('ldapmain', ldap) } + + describe '#users' do + before do + stub_ldap_config(base: 'dc=example,dc=com') + end + + it 'searches with the proper options when searching by uid' do + # Requires this expectation style to match the filter + expect(adapter).to receive(:ldap_search) do |arg| + expect(arg[:filter].to_s).to eq('(uid=johndoe)') + expect(arg[:base]).to eq('dc=example,dc=com') + expect(arg[:attributes]).to match(ldap_attributes) + end.and_return({}) + + adapter.users('uid', 'johndoe') + end + + it 'searches with the proper options when searching by dn' do + expect(adapter).to receive(:ldap_search).with( + base: 'uid=johndoe,ou=users,dc=example,dc=com', + scope: Net::LDAP::SearchScope_BaseObject, + attributes: ldap_attributes, + filter: nil + ).and_return({}) + + adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com') + end + + it 'searches with the proper options when searching with a limit' do + expect(adapter) + .to receive(:ldap_search).with(hash_including(size: 100)).and_return({}) + + adapter.users('uid', 'johndoe', 100) + end + + it 'returns an LDAP::Person if search returns a result' do + entry = ldap_user_entry('johndoe') + allow(adapter).to receive(:ldap_search).and_return([entry]) + + results = adapter.users('uid', 'johndoe') + + expect(results.size).to eq(1) + expect(results.first.uid).to eq('johndoe') + end + + it 'returns empty array if search entry does not respond to uid' do + entry = Net::LDAP::Entry.new + entry['dn'] = user_dn('johndoe') + allow(adapter).to receive(:ldap_search).and_return([entry]) + + results = adapter.users('uid', 'johndoe') + + expect(results).to be_empty + end + + it 'uses the right uid attribute when non-default' do + stub_ldap_config(uid: 'sAMAccountName') + expect(adapter).to receive(:ldap_search).with( + hash_including(attributes: ldap_attributes) + ).and_return({}) + + adapter.users('sAMAccountName', 'johndoe') + end + end + + describe '#dn_matches_filter?' do + subject { adapter.dn_matches_filter?(:dn, :filter) } + + context "when the search result is non-empty" do + before do + allow(adapter).to receive(:ldap_search).and_return([:foo]) + end + + it { is_expected.to be_truthy } + end + + context "when the search result is empty" do + before do + allow(adapter).to receive(:ldap_search).and_return([]) + end + + it { is_expected.to be_falsey } + end + end + + describe '#ldap_search' do + subject { adapter.ldap_search(base: :dn, filter: :filter) } + + context "when the search is successful" do + context "and the result is non-empty" do + before do + allow(ldap).to receive(:search).and_return([:foo]) + end + + it { is_expected.to eq [:foo] } + end + + context "and the result is empty" do + before do + allow(ldap).to receive(:search).and_return([]) + end + + it { is_expected.to eq [] } + end + end + + context "when the search encounters an error" do + before do + allow(ldap).to receive_messages( + search: nil, + get_operation_result: double(code: 1, message: 'some error') + ) + end + + it { is_expected.to eq [] } + end + + context "when the search raises an LDAP exception" do + before do + allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" } + allow(Rails.logger).to receive(:warn) + end + + it { is_expected.to eq [] } + + it 'logs the error' do + subject + expect(Rails.logger).to have_received(:warn).with( + "LDAP search raised exception Net::LDAP::Error: some error") + end + end + end + + def ldap_attributes + Gitlab::Auth::LDAP::Person.ldap_attributes(Gitlab::Auth::LDAP::Config.new('ldapmain')) + end +end diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb new file mode 100644 index 00000000000..05541972f87 --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::AuthHash do + include LdapHelpers + + let(:auth_hash) do + described_class.new( + OmniAuth::AuthHash.new( + uid: given_uid, + provider: 'ldapmain', + info: info, + extra: { + raw_info: raw_info + } + ) + ) + end + + let(:info) do + { + name: 'Smith, J.', + email: 'johnsmith@example.com', + nickname: '123456' + } + end + + let(:raw_info) do + { + uid: ['123456'], + email: ['johnsmith@example.com'], + cn: ['Smith, J.'], + fullName: ['John Smith'] + } + end + + context "without overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + + it "has the correct username" do + expect(auth_hash.username).to eq("123456") + end + + it "has the correct name" do + expect(auth_hash.name).to eq("Smith, J.") + end + end + + context "with overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + + let(:attributes) do + { + 'username' => %w(mail email), + 'name' => 'fullName' + } + end + + before do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:attributes).and_return(attributes) + end + + it "has the correct username" do + expect(auth_hash.username).to eq("johnsmith@example.com") + end + + it "has the correct name" do + expect(auth_hash.name).to eq("John Smith") + end + end + + describe '#uid' do + context 'when there is extraneous (but valid) whitespace' do + let(:given_uid) { 'uid =john smith , ou = people, dc= example,dc =com' } + + it 'removes the extraneous whitespace' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + + context 'when there are upper case characters' do + let(:given_uid) { 'UID=John Smith,ou=People,dc=example,dc=com' } + + it 'downcases' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + end + + describe '#username' do + context 'if lowercase_usernames setting is' do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + + before do + raw_info[:uid] = ['JOHN'] + end + + it 'enabled the username attribute is lower cased' do + stub_ldap_config(lowercase_usernames: true) + + expect(auth_hash.username).to eq 'john' + end + + it 'disabled the username attribute is not lower cased' do + stub_ldap_config(lowercase_usernames: false) + + expect(auth_hash.username).to eq 'JOHN' + end + end + end +end diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb new file mode 100644 index 00000000000..111572d043b --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::Authentication do + let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' } + let(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::LDAP::Person.normalize_dn(dn)) } + let(:login) { 'john' } + let(:password) { 'password' } + + describe 'login' do + before do + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) + end + + it "finds the user if authentication is successful" do + expect(user).not_to be_nil + + # try only to fake the LDAP call + adapter = double('adapter', dn: dn).as_null_object + allow_any_instance_of(described_class) + .to receive(:adapter).and_return(adapter) + + expect(described_class.login(login, password)).to be_truthy + end + + it "is false if the user does not exist" do + # try only to fake the LDAP call + adapter = double('adapter', dn: dn).as_null_object + allow_any_instance_of(described_class) + .to receive(:adapter).and_return(adapter) + + expect(described_class.login(login, password)).to be_falsey + end + + it "is false if authentication fails" do + expect(user).not_to be_nil + + # try only to fake the LDAP call + adapter = double('adapter', bind_as: nil).as_null_object + allow_any_instance_of(described_class) + .to receive(:adapter).and_return(adapter) + + expect(described_class.login(login, password)).to be_falsey + end + + it "fails if ldap is disabled" do + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(false) + expect(described_class.login(login, password)).to be_falsey + end + + it "fails if no login is supplied" do + expect(described_class.login('', password)).to be_falsey + end + + it "fails if no password is supplied" do + expect(described_class.login(login, '')).to be_falsey + end + end +end diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb new file mode 100644 index 00000000000..82587e2ba55 --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -0,0 +1,373 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::Config do + include LdapHelpers + + let(:config) { described_class.new('ldapmain') } + + describe '.servers' do + it 'returns empty array if no server information is available' do + allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false) + + expect(described_class.servers).to eq [] + end + end + + describe '#initialize' do + it 'requires a provider' do + expect { described_class.new }.to raise_error ArgumentError + end + + it 'works' do + expect(config).to be_a described_class + end + + it 'raises an error if a unknown provider is used' do + expect { described_class.new 'unknown' }.to raise_error(RuntimeError) + end + end + + describe '#adapter_options' do + it 'constructs basic options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 386, + 'encryption' => 'plain' + } + ) + + expect(config.adapter_options).to eq( + host: 'ldap.example.com', + port: 386, + encryption: nil + ) + end + + it 'includes authentication options when auth is configured' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true, + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => 'super_secret' + } + ) + + expect(config.adapter_options).to include({ + auth: { + method: :simple, + username: 'uid=admin,dc=example,dc=com', + password: 'super_secret' + } + }) + end + + it 'sets encryption method to simple_tls when configured as simple_tls' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls' + } + ) + + expect(config.adapter_options[:encryption]).to include({ method: :simple_tls }) + end + + it 'sets encryption method to start_tls when configured as start_tls' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls' + } + ) + + expect(config.adapter_options[:encryption]).to include({ method: :start_tls }) + end + + context 'when verify_certificates is enabled' do + it 'sets tls_options to OpenSSL defaults' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true + } + ) + + expect(config.adapter_options[:encryption]).to include({ tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS }) + end + end + + context 'when verify_certificates is disabled' do + it 'sets verify_mode to OpenSSL VERIFY_NONE' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => false + } + ) + + expect(config.adapter_options[:encryption]).to include({ + tls_options: { + verify_mode: OpenSSL::SSL::VERIFY_NONE + } + }) + end + end + + context 'when ca_file is specified' do + it 'passes it through in tls_options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'ca_file' => '/etc/ca.pem' + } + ) + + expect(config.adapter_options[:encryption][:tls_options]).to include({ ca_file: '/etc/ca.pem' }) + end + end + + context 'when ca_file is a blank string' do + it 'does not add the ca_file key to tls_options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'ca_file' => ' ' + } + ) + + expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ca_file) + end + end + + context 'when ssl_version is specified' do + it 'passes it through in tls_options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'ssl_version' => 'TLSv1_2' + } + ) + + expect(config.adapter_options[:encryption][:tls_options]).to include({ ssl_version: 'TLSv1_2' }) + end + end + + context 'when ssl_version is a blank string' do + it 'does not add the ssl_version key to tls_options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'ssl_version' => ' ' + } + ) + + expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ssl_version) + end + end + end + + describe '#omniauth_options' do + it 'constructs basic options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 386, + 'base' => 'ou=users,dc=example,dc=com', + 'encryption' => 'plain', + 'uid' => 'uid' + } + ) + + expect(config.omniauth_options).to include( + host: 'ldap.example.com', + port: 386, + base: 'ou=users,dc=example,dc=com', + encryption: 'plain', + filter: '(uid=%{username})' + ) + expect(config.omniauth_options.keys).not_to include(:bind_dn, :password) + end + + it 'includes authentication options when auth is configured' do + stub_ldap_config( + options: { + 'uid' => 'sAMAccountName', + 'user_filter' => '(memberOf=cn=group1,ou=groups,dc=example,dc=com)', + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => 'super_secret' + } + ) + + expect(config.omniauth_options).to include( + filter: '(&(sAMAccountName=%{username})(memberOf=cn=group1,ou=groups,dc=example,dc=com))', + bind_dn: 'uid=admin,dc=example,dc=com', + password: 'super_secret' + ) + end + + context 'when verify_certificates is enabled' do + it 'specifies disable_verify_certificates as false' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true + } + ) + + expect(config.omniauth_options).to include({ disable_verify_certificates: false }) + end + end + + context 'when verify_certificates is disabled' do + it 'specifies disable_verify_certificates as true' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => false + } + ) + + expect(config.omniauth_options).to include({ disable_verify_certificates: true }) + end + end + + context 'when ca_file is present' do + it 'passes it through' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true, + 'ca_file' => '/etc/ca.pem' + } + ) + + expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' }) + end + end + + context 'when ca_file is blank' do + it 'does not include the ca_file option' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true, + 'ca_file' => ' ' + } + ) + + expect(config.omniauth_options).not_to have_key(:ca_file) + end + end + + context 'when ssl_version is present' do + it 'passes it through' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true, + 'ssl_version' => 'TLSv1_2' + } + ) + + expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' }) + end + end + + context 'when ssl_version is blank' do + it 'does not include the ssl_version option' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'simple_tls', + 'verify_certificates' => true, + 'ssl_version' => ' ' + } + ) + + expect(config.omniauth_options).not_to have_key(:ssl_version) + end + end + end + + describe '#has_auth?' do + it 'is true when password is set' do + stub_ldap_config( + options: { + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => 'super_secret' + } + ) + + expect(config.has_auth?).to be_truthy + end + + it 'is true when bind_dn is set and password is empty' do + stub_ldap_config( + options: { + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => '' + } + ) + + expect(config.has_auth?).to be_truthy + end + + it 'is false when password and bind_dn are not set' do + stub_ldap_config(options: { 'bind_dn' => nil, 'password' => nil }) + + expect(config.has_auth?).to be_falsey + end + end + + describe '#attributes' do + it 'uses default attributes when no custom attributes are configured' do + expect(config.attributes).to eq(config.default_attributes) + end + + it 'merges the configuration attributes with default attributes' do + stub_ldap_config( + options: { + 'attributes' => { + 'username' => %w(sAMAccountName), + 'email' => %w(userPrincipalName) + } + } + ) + + expect(config.attributes).to include({ + 'username' => %w(sAMAccountName), + 'email' => %w(userPrincipalName), + 'name' => 'cn' + }) + end + end +end diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb new file mode 100644 index 00000000000..f2983a02602 --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::DN do + using RSpec::Parameterized::TableSyntax + + describe '#normalize_value' do + subject { described_class.normalize_value(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { '"Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { '"James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'foo\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + end + end + + describe '#to_normalized_s' do + subject { described_class.new(given).to_normalized_s } + + it_behaves_like 'normalizes a DN' + + context 'when we do not support the given DN format' do + context 'multivalued RDNs' do + context 'without extraneous whitespace' do + let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError) + end + end + + context 'with extraneous whitespace' do + context 'around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError) + end + end + + context 'not around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError) + end + end + end + end + end + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'uid=John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { 'uid="Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'without a name value pair' do + let(:given) { 'John' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { 'cn="James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'cn=J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'cn=\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid OID attribute type name' do + let(:given) { '1.2.d=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"') + end + end + + context 'with a period in a non-OID attribute type name' do + let(:given) { 'd1.2=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."') + end + end + + context 'when starting with non-space, non-alphanumeric character' do + let(:given) { ' -uid=John Smith' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"') + end + end + + context 'when given a UID with an escaped equal sign' do + let(:given) { 'uid\\=john' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"') + end + end + end + end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: \"#{expected}\"\n got: \"#{got}\"" + expect(got).to eq(expected), test_failure_message + end +end diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb new file mode 100644 index 00000000000..1527fe60fb9 --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/person_spec.rb @@ -0,0 +1,160 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::Person do + include LdapHelpers + + let(:entry) { ldap_user_entry('john.doe') } + + before do + stub_ldap_config( + options: { + 'uid' => 'uid', + 'attributes' => { + 'name' => 'cn', + 'email' => %w(mail email userPrincipalName), + 'username' => username_attribute + } + } + ) + end + let(:username_attribute) { %w(uid sAMAccountName userid) } + + describe '.normalize_dn' do + subject { described_class.normalize_dn(given) } + + it_behaves_like 'normalizes a DN' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given DN unmodified' do + expect(subject).to eq(given) + end + end + end + + describe '.normalize_uid' do + subject { described_class.normalize_uid(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given UID unmodified' do + expect(subject).to eq(given) + end + end + end + + describe '.ldap_attributes' do + it 'returns a compact and unique array' do + stub_ldap_config( + options: { + 'uid' => nil, + 'attributes' => { + 'name' => 'cn', + 'email' => 'mail', + 'username' => %w(uid mail memberof) + } + } + ) + config = Gitlab::Auth::LDAP::Config.new('ldapmain') + ldap_attributes = described_class.ldap_attributes(config) + + expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof)) + end + end + + describe '#name' do + it 'uses the configured name attribute and handles values as an array' do + name = 'John Doe' + entry['cn'] = [name] + person = described_class.new(entry, 'ldapmain') + + expect(person.name).to eq(name) + end + end + + describe '#email' do + it 'returns the value of mail, if present' do + mail = 'john@example.com' + entry['mail'] = mail + person = described_class.new(entry, 'ldapmain') + + expect(person.email).to eq([mail]) + end + + it 'returns the value of userPrincipalName, if mail and email are not present' do + user_principal_name = 'john.doe@example.com' + entry['userPrincipalName'] = user_principal_name + person = described_class.new(entry, 'ldapmain') + + expect(person.email).to eq([user_principal_name]) + end + end + + describe '#username' do + context 'with default uid username attribute' do + let(:username_attribute) { 'uid' } + + it 'returns the proper username value' do + attr_value = 'johndoe' + entry[username_attribute] = attr_value + person = described_class.new(entry, 'ldapmain') + + expect(person.username).to eq(attr_value) + end + end + + context 'with a different username attribute' do + let(:username_attribute) { 'sAMAccountName' } + + it 'returns the proper username value' do + attr_value = 'johndoe' + entry[username_attribute] = attr_value + person = described_class.new(entry, 'ldapmain') + + expect(person.username).to eq(attr_value) + end + end + + context 'with a non-standard username attribute' do + let(:username_attribute) { 'mail' } + + it 'returns the proper username value' do + attr_value = 'john.doe@example.com' + entry[username_attribute] = attr_value + person = described_class.new(entry, 'ldapmain') + + expect(person.username).to eq(attr_value) + end + end + + context 'if lowercase_usernames setting is' do + let(:username_attribute) { 'uid' } + + before do + entry[username_attribute] = 'JOHN' + @person = described_class.new(entry, 'ldapmain') + end + + it 'enabled the username attribute is lower cased' do + stub_ldap_config(lowercase_usernames: true) + + expect(@person.username).to eq 'john' + end + + it 'disabled the username attribute is not lower cased' do + stub_ldap_config(lowercase_usernames: false) + + expect(@person.username).to eq 'JOHN' + end + end + end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}" + expect(got).to eq(expected), test_failure_message + end +end diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb new file mode 100644 index 00000000000..cab2169593a --- /dev/null +++ b/spec/lib/gitlab/auth/ldap/user_spec.rb @@ -0,0 +1,241 @@ +require 'spec_helper' + +describe Gitlab::Auth::LDAP::User do + let(:ldap_user) { described_class.new(auth_hash) } + let(:gl_user) { ldap_user.gl_user } + let(:info) do + { + name: 'John', + email: 'john@example.com', + nickname: 'john' + } + end + let(:auth_hash) do + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info) + end + let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) } + let(:info_upper_case) do + { + name: 'John', + email: 'John@Example.com', # Email address has upper case chars + nickname: 'john' + } + end + let(:auth_hash_upper_case) do + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info_upper_case) + end + + describe '#changed?' do + it "marks existing ldap user as changed" do + create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain') + expect(ldap_user.changed?).to be_truthy + end + + it "marks existing non-ldap user if the email matches as changed" do + create(:user, email: 'john@example.com') + expect(ldap_user.changed?).to be_truthy + end + + it "does not mark existing ldap user as changed" do + create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') + expect(ldap_user.changed?).to be_falsey + end + end + + describe '.find_by_uid_and_provider' do + let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' } + + it 'retrieves the correct user' do + special_info = { + name: 'John Åström', + email: 'john@example.com', + nickname: 'jastrom' + } + special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info) + special_chars_user = described_class.new(special_hash) + user = special_chars_user.save + + expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user + end + end + + describe 'find or create' do + it "finds the user if already existing" do + create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') + + expect { ldap_user.save }.not_to change { User.count } + end + + it "connects to existing non-ldap user if the email matches" do + existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") + expect { ldap_user.save }.not_to change { User.count } + + existing_user.reload + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' + end + + it 'connects to existing ldap user if the extern_uid changes' do + existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') + expect { ldap_user.save }.not_to change { User.count } + + existing_user.reload + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' + expect(existing_user.id).to eql ldap_user.gl_user.id + end + + it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do + existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') + expect { ldap_user_upper_case.save }.not_to change { User.count } + + existing_user.reload + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' + expect(existing_user.id).to eql ldap_user.gl_user.id + end + + it 'maintains an identity per provider' do + existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter') + expect(existing_user.identities.count).to be(1) + + ldap_user.save + expect(ldap_user.gl_user.identities.count).to be(2) + + # Expect that find_by provider only returns a single instance of an identity and not an Enumerable + expect(ldap_user.gl_user.identities.find_by(provider: 'twitter')).to be_instance_of Identity + expect(ldap_user.gl_user.identities.find_by(provider: auth_hash.provider)).to be_instance_of Identity + end + + it "creates a new user if not found" do + expect { ldap_user.save }.to change { User.count }.by(1) + end + + context 'when signup is disabled' do + before do + stub_application_setting signup_enabled: false + end + + it 'creates the user' do + ldap_user.save + + expect(gl_user).to be_persisted + end + end + + context 'when user confirmation email is enabled' do + before do + stub_application_setting send_user_confirmation_email: true + end + + it 'creates and confirms the user anyway' do + ldap_user.save + + expect(gl_user).to be_persisted + expect(gl_user).to be_confirmed + end + end + end + + describe 'updating email' do + context "when LDAP sets an email" do + it "has a real email" do + expect(ldap_user.gl_user.email).to eq(info[:email]) + end + + it "has email set as synced" do + expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has email set as read-only" do + expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do + expect(ldap_user.gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + end + end + + context "when LDAP doesn't set an email" do + before do + info.delete(:email) + end + + it "has a temp email" do + expect(ldap_user.gl_user.temp_oauth_email?).to be_truthy + end + + it "has email set as not synced" do + expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_falsey + end + + it "does not have email set as read-only" do + expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_falsey + end + end + end + + describe 'blocking' do + def configure_block(value) + allow_any_instance_of(Gitlab::Auth::LDAP::Config) + .to receive(:block_auto_created_users).and_return(value) + end + + context 'signup' do + context 'dont block on create' do + before do + configure_block(false) + end + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + configure_block(true) + end + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'sign-in' do + before do + ldap_user.save + ldap_user.gl_user.activate + end + + context 'dont block on create' do + before do + configure_block(false) + end + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + configure_block(true) + end + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end +end diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb new file mode 100644 index 00000000000..40001cea22e --- /dev/null +++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Gitlab::Auth::OAuth::AuthHash do + let(:provider) { 'ldap'.freeze } + let(:auth_hash) do + described_class.new( + OmniAuth::AuthHash.new( + provider: provider, + uid: uid_ascii, + info: info_hash + ) + ) + end + + let(:uid_raw) do + "CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net" + end + let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" } + let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" } + let(:first_name_raw) { 'Onur' } + let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" } + let(:name_raw) { "Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" } + + let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:nickname_ascii) { nickname_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:first_name_ascii) { first_name_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:last_name_ascii) { last_name_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:name_ascii) { name_raw.force_encoding(Encoding::ASCII_8BIT) } + + let(:uid_utf8) { uid_ascii.force_encoding(Encoding::UTF_8) } + 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(:info_hash) do + { + email: email_ascii, + first_name: first_name_ascii, + last_name: last_name_ascii, + name: name_ascii, + nickname: nickname_ascii, + uid: uid_ascii + } + end + + context 'defaults' do + it { expect(auth_hash.provider).to eq provider } + it { expect(auth_hash.uid).to eql uid_utf8 } + it { expect(auth_hash.email).to eql email_utf8 } + it { expect(auth_hash.username).to eql nickname_utf8 } + it { expect(auth_hash.name).to eql name_utf8 } + it { expect(auth_hash.password).not_to be_empty } + end + + context 'email not provided' do + before do + info_hash.delete(:email) + end + + it 'generates a temp email' do + expect( auth_hash.email).to start_with('temp-email-for-oauth') + end + end + + context 'username not provided' do + before do + info_hash.delete(:nickname) + end + + it 'takes the first part of the email as username' do + expect(auth_hash.username).to eql 'onur.kucuk_ABC-123' + end + end + + context 'name not provided' do + before do + info_hash.delete(:name) + end + + it 'concats first and lastname as the name' do + expect(auth_hash.name).to eql name_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 + end + + it 'forces utf8 encoding on provider' do + expect(auth_hash.provider.encoding).to eql Encoding::UTF_8 + end + + it 'forces utf8 encoding on name' do + expect(auth_hash.name.encoding).to eql Encoding::UTF_8 + end + + it 'forces utf8 encoding on username' do + expect(auth_hash.username.encoding).to eql Encoding::UTF_8 + end + + it 'forces utf8 encoding on email' do + expect(auth_hash.email.encoding).to eql Encoding::UTF_8 + end + + it 'forces utf8 encoding on password' do + expect(auth_hash.password.encoding).to eql Encoding::UTF_8 + end + end +end diff --git a/spec/lib/gitlab/auth/o_auth/provider_spec.rb b/spec/lib/gitlab/auth/o_auth/provider_spec.rb new file mode 100644 index 00000000000..fc35d430917 --- /dev/null +++ b/spec/lib/gitlab/auth/o_auth/provider_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::Auth::OAuth::Provider do + describe '#config_for' do + context 'for an LDAP provider' do + context 'when the provider exists' do + it 'returns the config' do + expect(described_class.config_for('ldapmain')).to be_a(Hash) + end + end + + context 'when the provider does not exist' do + it 'returns nil' do + expect(described_class.config_for('ldapfoo')).to be_nil + end + end + end + + context 'for an OmniAuth provider' do + before do + provider = OpenStruct.new( + name: 'google', + app_id: 'asd123', + app_secret: 'asd123' + ) + allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider]) + end + + context 'when the provider exists' do + it 'returns the config' do + expect(described_class.config_for('google')).to be_a(OpenStruct) + end + end + + context 'when the provider does not exist' do + it 'returns nil' do + expect(described_class.config_for('foo')).to be_nil + end + end + end + end +end diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb new file mode 100644 index 00000000000..0c71f1d8ca6 --- /dev/null +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -0,0 +1,742 @@ +require 'spec_helper' + +describe Gitlab::Auth::OAuth::User do + let(:oauth_user) { described_class.new(auth_hash) } + let(:gl_user) { oauth_user.gl_user } + let(:uid) { 'my-uid' } + let(:dn) { 'uid=user1,ou=people,dc=example' } + let(:provider) { 'my-provider' } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) } + let(:info_hash) do + { + nickname: '-john+gitlab-ETC%.git@gmail.com', + name: 'John', + email: 'john@mail.com', + address: { + locality: 'locality', + country: 'country' + } + } + end + let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } + + describe '#persisted?' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + it "finds an existing user based on uid and provider (facebook)" do + expect( oauth_user.persisted? ).to be_truthy + end + + it 'returns false if user is not found in database' do + allow(auth_hash).to receive(:uid).and_return('non-existing') + expect( oauth_user.persisted? ).to be_falsey + end + end + + def stub_omniauth_config(messages) + allow(Gitlab.config.omniauth).to receive_messages(messages) + end + + describe '#save' do + def stub_ldap_config(messages) + allow(Gitlab::Auth::LDAP::Config).to receive_messages(messages) + end + + let(:provider) { 'twitter' } + + describe 'when account exists on server' do + it 'does not mark the user as external' do + create(:omniauth_user, extern_uid: 'my-uid', provider: provider) + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) + + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + + describe 'signup' do + context 'when signup is disabled' do + before do + stub_application_setting signup_enabled: false + end + + it 'creates the user' do + stub_omniauth_config(allow_single_sign_on: [provider]) + + oauth_user.save + + expect(gl_user).to be_persisted + end + end + + context 'when user confirmation email is enabled' do + before do + stub_application_setting send_user_confirmation_email: true + end + + it 'creates and confirms the user anyway' do + stub_omniauth_config(allow_single_sign_on: [provider]) + + oauth_user.save + + expect(gl_user).to be_persisted + expect(gl_user).to be_confirmed + end + end + + it 'marks user as having password_automatically_set' do + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) + + oauth_user.save + + expect(gl_user).to be_persisted + expect(gl_user).to be_password_automatically_set + end + + shared_examples 'to verify compliance with allow_single_sign_on' do + context 'provider is marked as external' do + it 'marks user as external' do + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + context 'provider was external, now has been removed' do + it 'does not mark external user as internal' do + create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true) + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook']) + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + context 'provider is not external' do + context 'when adding a new OAuth identity' do + it 'does not promote an external user to internal' do + user = create(:user, email: 'john@mail.com', external: true) + user.identities.create(provider: provider, extern_uid: uid) + + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + end + + context 'with new allow_single_sign_on enabled syntax' do + before do + stub_omniauth_config(allow_single_sign_on: [provider]) + end + + 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 provider + end + end + + context "with old allow_single_sign_on enabled syntax" do + before do + stub_omniauth_config(allow_single_sign_on: true) + end + + 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 provider + end + end + + context 'with new allow_single_sign_on disabled syntax' do + before do + stub_omniauth_config(allow_single_sign_on: []) + end + + it 'throws an error' do + expect { oauth_user.save }.to raise_error StandardError + end + end + + context 'with old allow_single_sign_on disabled (Default)' do + before do + stub_omniauth_config(allow_single_sign_on: false) + end + + it 'throws an error' do + expect { oauth_user.save }.to raise_error StandardError + end + end + end + + context "with auto_link_ldap_user disabled (default)" do + before do + stub_omniauth_config(auto_link_ldap_user: false) + end + + include_examples "to verify compliance with allow_single_sign_on" + end + + context "with auto_link_ldap_user enabled" do + before do + stub_omniauth_config(auto_link_ldap_user: true) + end + + context "and no LDAP provider defined" do + before do + stub_ldap_config(providers: []) + end + + include_examples "to verify compliance with allow_single_sign_on" + end + + context "and at least one LDAP provider is defined" do + before do + stub_ldap_config(providers: %w(ldapmain)) + end + + context "and a corresponding LDAP person" do + before do + allow(ldap_user).to receive(:uid) { uid } + allow(ldap_user).to receive(:username) { uid } + allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } + allow(ldap_user).to receive(:dn) { dn } + end + + context "and no account for the LDAP user" do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) + + oauth_user.save + end + + it "creates a user with dual LDAP and omniauth identities" do + 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 be 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: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end + + it "has email set as synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + end + end + + context "and LDAP user has an account already" do + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } + it "adds the omniauth identity to the LDAP account" do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) + + 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 be 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: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end + end + + context 'when an LDAP person is not found by uid' do + it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) + + oauth_user.save + + 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: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end + end + end + + context 'and a corresponding LDAP person with a non-default username' do + before do + allow(ldap_user).to receive(:uid) { uid } + allow(ldap_user).to receive(:username) { 'johndoe@example.com' } + allow(ldap_user).to receive(:email) { %w(johndoe@example.com john2@example.com) } + allow(ldap_user).to receive(:dn) { dn } + end + + context 'and no account for the LDAP user' do + it 'creates a user favoring the LDAP username and strips email domain' do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) + + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'johndoe' + end + end + end + + context "and no corresponding LDAP person" do + before do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil) + end + + include_examples "to verify compliance with allow_single_sign_on" + end + end + end + end + + describe 'blocking' do + let(:provider) { 'twitter' } + + before do + stub_omniauth_config(allow_single_sign_on: ['twitter']) + end + + context 'signup with omniauth only' do + context 'dont block on create' do + before do + stub_omniauth_config(block_auto_created_users: false) + end + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + stub_omniauth_config(block_auto_created_users: true) + end + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'signup with linked omniauth and LDAP account' do + before do + stub_omniauth_config(auto_link_ldap_user: true) + allow(ldap_user).to receive(:uid) { uid } + allow(ldap_user).to receive(:username) { uid } + allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } + allow(ldap_user).to receive(:dn) { dn } + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) + end + + context "and no account for the LDAP user" do + context 'dont block on create (LDAP)' do + before do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + end + + 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 do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + end + + 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: dn, provider: 'ldapmain', username: 'john') } + + context 'dont block on create (LDAP)' do + before do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + end + + 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 do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + end + + 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 + oauth_user.gl_user.activate + end + + context 'dont block on create' do + before do + stub_omniauth_config(block_auto_created_users: false) + end + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + stub_omniauth_config(block_auto_created_users: true) + end + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'dont block on create (LDAP)' do + before do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + end + + 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 do + allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + end + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end + end + + describe 'ensure backwards compatibility with with sync email from provider option' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + before do + stub_omniauth_config(sync_email_from_provider: 'my-provider') + stub_omniauth_config(sync_profile_from_provider: ['my-provider']) + end + + context "when provider sets an email" do + it "updates the user email" do + expect(gl_user.email).to eq(info_hash[:email]) + end + + it "has email set as synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to my-provider" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' + end + end + + context "when provider doesn't set an email" do + before do + info_hash.delete(:email) + end + + it "does not update the user email" do + expect(gl_user.email).not_to eq(info_hash[:email]) + end + + it "has email set as not synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey + end + + it "does not have email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_falsey + end + end + end + + describe 'generating username' do + context 'when no collision with existing user' do + it 'generates the username with no counter' do + expect(gl_user.username).to eq('johngitlab-ETC') + end + end + + context 'when collision with existing user' do + it 'generates the username with a counter' do + oauth_user.save + oauth_user2 = described_class.new(OmniAuth::AuthHash.new(uid: 'my-uid2', provider: provider, info: { nickname: 'johngitlab-ETC@othermail.com', email: 'john@othermail.com' })) + + expect(oauth_user2.gl_user.username).to eq('johngitlab-ETC1') + end + end + + context 'when username is a reserved word' do + let(:info_hash) do + { + nickname: 'admin@othermail.com', + email: 'admin@othermail.com' + } + end + + it 'generates the username with a counter' do + expect(gl_user.username).to eq('admin1') + end + end + end + + describe 'updating email with sync profile' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + before do + stub_omniauth_config(sync_profile_from_provider: ['my-provider']) + stub_omniauth_config(sync_profile_attributes: true) + end + + context "when provider sets an email" do + it "updates the user email" do + expect(gl_user.email).to eq(info_hash[:email]) + end + + it "has email set as synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) + end + + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to my-provider" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' + end + end + + context "when provider doesn't set an email" do + before do + info_hash.delete(:email) + end + + it "does not update the user email" do + expect(gl_user.email).not_to eq(info_hash[:email]) + end + + it "has email set as not synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey + end + + it "does not have email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_falsey + end + end + end + + describe 'updating name' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + before do + stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) + stub_omniauth_setting(sync_profile_attributes: true) + end + + context "when provider sets a name" do + it "updates the user name" do + expect(gl_user.name).to eq(info_hash[:name]) + end + end + + context "when provider doesn't set a name" do + before do + info_hash.delete(:name) + end + + it "does not update the user name" do + expect(gl_user.name).not_to eq(info_hash[:name]) + expect(gl_user.user_synced_attributes_metadata.name_synced).to be(false) + end + end + end + + describe 'updating location' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + before do + stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) + stub_omniauth_setting(sync_profile_attributes: true) + end + + context "when provider sets a location" do + it "updates the user location" do + expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) + expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) + end + end + + context "when provider doesn't set a location" do + before do + info_hash[:address].delete(:country) + info_hash[:address].delete(:locality) + end + + it "does not update the user location" do + expect(gl_user.location).to be_nil + expect(gl_user.user_synced_attributes_metadata.location_synced).to be(false) + end + end + end + + describe 'updating user info' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + context "update all info" do + before do + stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) + stub_omniauth_setting(sync_profile_attributes: true) + end + + it "updates the user email" do + expect(gl_user.email).to eq(info_hash[:email]) + expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) + end + + it "updates the user name" do + expect(gl_user.name).to eq(info_hash[:name]) + expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true) + end + + it "updates the user location" do + expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) + expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) + end + + it "sets my-provider as the attributes provider" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql('my-provider') + end + end + + context "update only requested info" do + before do + stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) + stub_omniauth_setting(sync_profile_attributes: %w(name location)) + end + + it "updates the user name" do + expect(gl_user.name).to eq(info_hash[:name]) + expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true) + end + + it "updates the user location" do + expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) + expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) + end + + it "does not update the user email" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false) + end + end + + context "update default_scope" do + before do + stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) + end + + it "updates the user email" do + expect(gl_user.email).to eq(info_hash[:email]) + expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) + end + end + + context "update no info when profile sync is nil" do + it "does not have sync_attribute" do + expect(gl_user.user_synced_attributes_metadata).to be(nil) + end + + it "does not update the user email" do + expect(gl_user.email).not_to eq(info_hash[:email]) + end + + it "does not update the user name" do + expect(gl_user.name).not_to eq(info_hash[:name]) + end + + it "does not update the user location" do + expect(gl_user.location).not_to eq(info_hash[:address][:country]) + end + + it 'does not create associated user synced attributes metadata' do + expect(gl_user.user_synced_attributes_metadata).to be_nil + end + end + end + + describe '.find_by_uid_and_provider' do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + it 'normalizes extern_uid' do + allow(oauth_user.auth_hash).to receive(:uid).and_return('MY-UID') + expect(oauth_user.find_user).to eql gl_user + end + end +end diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb new file mode 100644 index 00000000000..bb950e6bbf8 --- /dev/null +++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Auth::Saml::AuthHash do + include LoginHelpers + + let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } } + subject(:saml_auth_hash) { described_class.new(omniauth_auth_hash) } + + let(:info_hash) do + { + name: 'John', + email: 'john@mail.com' + } + end + + let(:omniauth_auth_hash) do + OmniAuth::AuthHash.new(uid: 'my-uid', + provider: 'saml', + info: info_hash, + extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) } ) + end + + before do + stub_saml_group_config(%w(Developers Freelancers Designers)) + end + + describe '#groups' do + it 'returns array of groups' do + expect(saml_auth_hash.groups).to eq(%w(Developers Freelancers)) + end + + context 'raw info hash attributes empty' do + let(:raw_info_attr) { {} } + + it 'returns an empty array' do + expect(saml_auth_hash.groups).to be_a(Array) + end + end + end +end diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb new file mode 100644 index 00000000000..62514ca0688 --- /dev/null +++ b/spec/lib/gitlab/auth/saml/user_spec.rb @@ -0,0 +1,403 @@ +require 'spec_helper' + +describe Gitlab::Auth::Saml::User do + include LdapHelpers + include LoginHelpers + + let(:saml_user) { described_class.new(auth_hash) } + let(:gl_user) { saml_user.gl_user } + let(:uid) { 'my-uid' } + let(:dn) { 'uid=user1,ou=people,dc=example' } + let(:provider) { 'saml' } + let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers Designers) } } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) }) } + let(:info_hash) do + { + name: 'John', + email: 'john@mail.com' + } + end + let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } + + describe '#save' do + before do + stub_basic_saml_config + end + + describe 'account exists on server' do + before do + stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) + end + + let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } + + context 'and should bind with SAML' do + it 'adds the SAML identity to the existing user' do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user).to eq existing_user + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'saml' + end + end + + context 'external groups' do + context 'are defined' do + it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + before do + stub_saml_group_config(%w(Interns)) + end + + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + + context 'user was external, now should not be' do + it 'makes user internal' do + existing_user.update_attribute('external', true) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + end + end + + describe 'no account exists on server' do + shared_examples 'to verify compliance with allow_single_sign_on' do + context 'with allow_single_sign_on enabled' do + before do + stub_omniauth_config(allow_single_sign_on: ['saml']) + end + + it 'creates a user from SAML' do + saml_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 'saml' + end + end + + context 'with allow_single_sign_on default (["saml"])' do + before do + stub_omniauth_config(allow_single_sign_on: ['saml']) + end + + it 'does not throw an error' do + expect { saml_user.save }.not_to raise_error + end + end + + context 'with allow_single_sign_on disabled' do + before do + stub_omniauth_config(allow_single_sign_on: false) + end + + it 'throws an error' do + expect { saml_user.save }.to raise_error StandardError + end + end + end + + context 'external groups' do + context 'are defined' do + it 'marks the user as external' do + stub_saml_group_config(%w(Freelancers)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end + end + + context 'are defined but the user does not belong there' do + it 'does not mark the user as external' do + stub_saml_group_config(%w(Interns)) + saml_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + end + + context 'with auto_link_ldap_user disabled (default)' do + before do + stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) + end + + include_examples 'to verify compliance with allow_single_sign_on' + end + + context 'with auto_link_ldap_user enabled' do + before do + stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) + end + + context 'and at least one LDAP provider is defined' do + before do + stub_ldap_config(providers: %w(ldapmain)) + end + + context 'and a corresponding LDAP person' do + let(:adapter) { ldap_adapter('ldapmain') } + + before do + allow(ldap_user).to receive(:uid) { uid } + allow(ldap_user).to receive(:username) { uid } + allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } + allow(ldap_user).to receive(:dn) { dn } + allow(Gitlab::Auth::LDAP::Adapter).to receive(:new).and_return(adapter) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).with(dn, adapter).and_return(ldap_user) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user) + end + + context 'and no account for the LDAP user' do + it 'creates a user with dual LDAP and SAML identities' do + saml_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql uid + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 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: dn }, + { provider: 'saml', extern_uid: uid }]) + end + end + + context 'and LDAP user has an account already' do + let(:auth_hash_base_attributes) do + { + uid: uid, + provider: provider, + info: info_hash, + extra: { + raw_info: OneLogin::RubySaml::Attributes.new( + { 'groups' => %w(Developers Freelancers Designers) } + ) + } + } + end + let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes) } + let(:uid_types) { %w(uid dn email) } + + before do + create(:omniauth_user, + email: 'john@mail.com', + extern_uid: dn, + provider: 'ldapmain', + username: 'john') + end + + shared_examples 'find LDAP person' do |uid_type, uid| + let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes.merge(uid: extern_uid)) } + + before do + nil_types = uid_types - [uid_type] + + nil_types.each do |type| + allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil) + end + + allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user) + end + + it 'adds the omniauth identity to the LDAP account' do + identities = [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'saml', extern_uid: extern_uid } + ] + + identities_as_hash = gl_user.identities.map do |id| + { provider: id.provider, extern_uid: id.extern_uid } + end + + saml_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 2 + expect(identities_as_hash).to match_array(identities) + end + end + + context 'when uid is an uid' do + it_behaves_like 'find LDAP person', 'uid' do + let(:extern_uid) { uid } + end + end + + context 'when uid is a dn' do + it_behaves_like 'find LDAP person', 'dn' do + let(:extern_uid) { dn } + end + end + + context 'when uid is an email' do + it_behaves_like 'find LDAP person', 'email' do + let(:extern_uid) { 'john@mail.com' } + end + end + + it 'adds the omniauth identity to the LDAP account' do + saml_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 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: dn }, + { provider: 'saml', extern_uid: uid }]) + end + + it 'saves successfully on subsequent tries, when both identities are present' do + saml_user.save + local_saml_user = described_class.new(auth_hash) + local_saml_user.save + + expect(local_saml_user.gl_user).to be_valid + expect(local_saml_user.gl_user).to be_persisted + end + end + + context 'user has SAML user, and wants to add their LDAP identity' do + it 'adds the LDAP identity to the existing SAML user' do + create(:omniauth_user, email: 'john@mail.com', extern_uid: dn, provider: 'saml', username: 'john') + + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).with(dn, adapter).and_return(ldap_user) + + local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash) + local_saml_user = described_class.new(local_hash) + + local_saml_user.save + local_gl_user = local_saml_user.gl_user + + expect(local_gl_user).to be_valid + expect(local_gl_user.identities.length).to be 2 + identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: dn }, + { provider: 'saml', extern_uid: dn }]) + end + end + end + end + end + + context 'when signup is disabled' do + before do + stub_application_setting signup_enabled: false + end + + it 'creates the user' do + saml_user.save + + expect(gl_user).to be_persisted + end + end + + context 'when user confirmation email is enabled' do + before do + stub_application_setting send_user_confirmation_email: true + end + + it 'creates and confirms the user anyway' do + saml_user.save + + expect(gl_user).to be_persisted + expect(gl_user).to be_confirmed + end + end + end + + describe 'blocking' do + before do + stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) + end + + context 'signup with SAML only' do + context 'dont block on create' do + before do + stub_omniauth_config(block_auto_created_users: false) + end + + it 'does not block the user' do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + stub_omniauth_config(block_auto_created_users: true) + end + + it 'blocks user' do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'sign-in' do + before do + saml_user.save + saml_user.gl_user.activate + end + + context 'dont block on create' do + before do + stub_omniauth_config(block_auto_created_users: false) + end + + it do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before do + stub_omniauth_config(block_auto_created_users: true) + end + + it do + saml_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end + end + + describe '#find_user' do + context 'raw info hash attributes empty' do + let(:raw_info_attr) { {} } + + it 'does not mark user as external' do + stub_saml_group_config(%w(Freelancers)) + + expect(saml_user.find_user.external).to be_falsy + end + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index cc202ce8bca..f969f9e8e38 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -309,17 +309,17 @@ describe Gitlab::Auth do context "with ldap enabled" do before do - allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) end it "tries to autheticate with db before ldap" do - expect(Gitlab::LDAP::Authentication).not_to receive(:login) + expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login) gl_auth.find_with_user_password(username, password) end it "uses ldap as fallback to for authentication" do - expect(Gitlab::LDAP::Authentication).to receive(:login) + expect(Gitlab::Auth::LDAP::Authentication).to receive(:login) gl_auth.find_with_user_password('ldap_user', 'password') end @@ -336,7 +336,7 @@ describe Gitlab::Auth do context "with ldap enabled" do before do - allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) end it "does not find non-ldap user by valid login/password" do diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb deleted file mode 100644 index 6a47350be81..00000000000 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Access do - let(:access) { described_class.new user } - let(:user) { create(:omniauth_user) } - - describe '.allowed?' do - it 'updates the users `last_credential_check_at' do - expect(access).to receive(:allowed?) { true } - expect(described_class).to receive(:open).and_yield(access) - - expect { described_class.allowed?(user) } - .to change { user.last_credential_check_at } - end - end - - describe '#allowed?' do - subject { access.allowed? } - - context 'when the user cannot be found' do - before do - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) - end - - it { is_expected.to be_falsey } - - it 'blocks user in GitLab' do - expect(access).to receive(:block_user).with(user, 'does not exist anymore') - - access.allowed? - end - end - - context 'when the user is found' do - before do - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) - end - - context 'and the user is disabled via active directory' do - before do - allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true) - end - - it { is_expected.to be_falsey } - - it 'blocks user in GitLab' do - expect(access).to receive(:block_user).with(user, 'is disabled in Active Directory') - - access.allowed? - end - end - - context 'and has no disabled flag in active diretory' do - before do - allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) - end - - it { is_expected.to be_truthy } - - context 'when auto-created users are blocked' do - before do - user.block - end - - it 'does not unblock user in GitLab' do - expect(access).not_to receive(:unblock_user) - - access.allowed? - - expect(user).to be_blocked - expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic - end - end - - context 'when auto-created users are not blocked' do - before do - user.ldap_block - end - - it 'unblocks user in GitLab' do - expect(access).to receive(:unblock_user).with(user, 'is not disabled anymore') - - access.allowed? - end - end - end - - context 'without ActiveDirectory enabled' do - before do - allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) - allow_any_instance_of(Gitlab::LDAP::Config).to receive(:active_directory).and_return(false) - end - - it { is_expected.to be_truthy } - - context 'when user cannot be found' do - before do - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) - end - - it { is_expected.to be_falsey } - - it 'blocks user in GitLab' do - expect(access).to receive(:block_user).with(user, 'does not exist anymore') - - access.allowed? - end - end - - context 'when user was previously ldap_blocked' do - before do - user.ldap_block - end - - it 'unblocks the user if it exists' do - expect(access).to receive(:unblock_user).with(user, 'is available again') - - access.allowed? - end - end - end - end - end - - describe '#block_user' do - before do - user.activate - allow(Gitlab::AppLogger).to receive(:info) - - access.block_user user, 'reason' - end - - it 'blocks the user' do - expect(user).to be_blocked - expect(user).to be_ldap_blocked - end - - it 'logs the reason' do - expect(Gitlab::AppLogger).to have_received(:info).with( - "LDAP account \"123456\" reason, " \ - "blocking Gitlab user \"#{user.name}\" (#{user.email})" - ) - end - end - - describe '#unblock_user' do - before do - user.ldap_block - allow(Gitlab::AppLogger).to receive(:info) - - access.unblock_user user, 'reason' - end - - it 'activates the user' do - expect(user).not_to be_blocked - expect(user).not_to be_ldap_blocked - end - - it 'logs the reason' do - Gitlab::AppLogger.info( - "LDAP account \"123456\" reason, " \ - "unblocking Gitlab user \"#{user.name}\" (#{user.email})" - ) - end - end -end diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb deleted file mode 100644 index 6132abd9b35..00000000000 --- a/spec/lib/gitlab/ldap/adapter_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Adapter do - include LdapHelpers - - let(:ldap) { double(:ldap) } - let(:adapter) { ldap_adapter('ldapmain', ldap) } - - describe '#users' do - before do - stub_ldap_config(base: 'dc=example,dc=com') - end - - it 'searches with the proper options when searching by uid' do - # Requires this expectation style to match the filter - expect(adapter).to receive(:ldap_search) do |arg| - expect(arg[:filter].to_s).to eq('(uid=johndoe)') - expect(arg[:base]).to eq('dc=example,dc=com') - expect(arg[:attributes]).to match(ldap_attributes) - end.and_return({}) - - adapter.users('uid', 'johndoe') - end - - it 'searches with the proper options when searching by dn' do - expect(adapter).to receive(:ldap_search).with( - base: 'uid=johndoe,ou=users,dc=example,dc=com', - scope: Net::LDAP::SearchScope_BaseObject, - attributes: ldap_attributes, - filter: nil - ).and_return({}) - - adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com') - end - - it 'searches with the proper options when searching with a limit' do - expect(adapter) - .to receive(:ldap_search).with(hash_including(size: 100)).and_return({}) - - adapter.users('uid', 'johndoe', 100) - end - - it 'returns an LDAP::Person if search returns a result' do - entry = ldap_user_entry('johndoe') - allow(adapter).to receive(:ldap_search).and_return([entry]) - - results = adapter.users('uid', 'johndoe') - - expect(results.size).to eq(1) - expect(results.first.uid).to eq('johndoe') - end - - it 'returns empty array if search entry does not respond to uid' do - entry = Net::LDAP::Entry.new - entry['dn'] = user_dn('johndoe') - allow(adapter).to receive(:ldap_search).and_return([entry]) - - results = adapter.users('uid', 'johndoe') - - expect(results).to be_empty - end - - it 'uses the right uid attribute when non-default' do - stub_ldap_config(uid: 'sAMAccountName') - expect(adapter).to receive(:ldap_search).with( - hash_including(attributes: ldap_attributes) - ).and_return({}) - - adapter.users('sAMAccountName', 'johndoe') - end - end - - describe '#dn_matches_filter?' do - subject { adapter.dn_matches_filter?(:dn, :filter) } - - context "when the search result is non-empty" do - before do - allow(adapter).to receive(:ldap_search).and_return([:foo]) - end - - it { is_expected.to be_truthy } - end - - context "when the search result is empty" do - before do - allow(adapter).to receive(:ldap_search).and_return([]) - end - - it { is_expected.to be_falsey } - end - end - - describe '#ldap_search' do - subject { adapter.ldap_search(base: :dn, filter: :filter) } - - context "when the search is successful" do - context "and the result is non-empty" do - before do - allow(ldap).to receive(:search).and_return([:foo]) - end - - it { is_expected.to eq [:foo] } - end - - context "and the result is empty" do - before do - allow(ldap).to receive(:search).and_return([]) - end - - it { is_expected.to eq [] } - end - end - - context "when the search encounters an error" do - before do - allow(ldap).to receive_messages( - search: nil, - get_operation_result: double(code: 1, message: 'some error') - ) - end - - it { is_expected.to eq [] } - end - - context "when the search raises an LDAP exception" do - before do - allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" } - allow(Rails.logger).to receive(:warn) - end - - it { is_expected.to eq [] } - - it 'logs the error' do - subject - expect(Rails.logger).to have_received(:warn).with( - "LDAP search raised exception Net::LDAP::Error: some error") - end - end - end - - def ldap_attributes - Gitlab::LDAP::Person.ldap_attributes(Gitlab::LDAP::Config.new('ldapmain')) - end -end diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb deleted file mode 100644 index 9c30ddd7fe2..00000000000 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::AuthHash do - include LdapHelpers - - let(:auth_hash) do - described_class.new( - OmniAuth::AuthHash.new( - uid: given_uid, - provider: 'ldapmain', - info: info, - extra: { - raw_info: raw_info - } - ) - ) - end - - let(:info) do - { - name: 'Smith, J.', - email: 'johnsmith@example.com', - nickname: '123456' - } - end - - let(:raw_info) do - { - uid: ['123456'], - email: ['johnsmith@example.com'], - cn: ['Smith, J.'], - fullName: ['John Smith'] - } - end - - context "without overridden attributes" do - let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } - - it "has the correct username" do - expect(auth_hash.username).to eq("123456") - end - - it "has the correct name" do - expect(auth_hash.name).to eq("Smith, J.") - end - end - - context "with overridden attributes" do - let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } - - let(:attributes) do - { - 'username' => %w(mail email), - 'name' => 'fullName' - } - end - - before do - allow_any_instance_of(Gitlab::LDAP::Config).to receive(:attributes).and_return(attributes) - end - - it "has the correct username" do - expect(auth_hash.username).to eq("johnsmith@example.com") - end - - it "has the correct name" do - expect(auth_hash.name).to eq("John Smith") - end - end - - describe '#uid' do - context 'when there is extraneous (but valid) whitespace' do - let(:given_uid) { 'uid =john smith , ou = people, dc= example,dc =com' } - - it 'removes the extraneous whitespace' do - expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') - end - end - - context 'when there are upper case characters' do - let(:given_uid) { 'UID=John Smith,ou=People,dc=example,dc=com' } - - it 'downcases' do - expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') - end - end - end - - describe '#username' do - context 'if lowercase_usernames setting is' do - let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } - - before do - raw_info[:uid] = ['JOHN'] - end - - it 'enabled the username attribute is lower cased' do - stub_ldap_config(lowercase_usernames: true) - - expect(auth_hash.username).to eq 'john' - end - - it 'disabled the username attribute is not lower cased' do - stub_ldap_config(lowercase_usernames: false) - - expect(auth_hash.username).to eq 'JOHN' - end - end - end -end diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb deleted file mode 100644 index 9d57a46c12b..00000000000 --- a/spec/lib/gitlab/ldap/authentication_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Authentication do - let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' } - let(:user) { create(:omniauth_user, extern_uid: Gitlab::LDAP::Person.normalize_dn(dn)) } - let(:login) { 'john' } - let(:password) { 'password' } - - describe 'login' do - before do - allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) - end - - it "finds the user if authentication is successful" do - expect(user).not_to be_nil - - # try only to fake the LDAP call - adapter = double('adapter', dn: dn).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) - - expect(described_class.login(login, password)).to be_truthy - end - - it "is false if the user does not exist" do - # try only to fake the LDAP call - adapter = double('adapter', dn: dn).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) - - expect(described_class.login(login, password)).to be_falsey - end - - it "is false if authentication fails" do - expect(user).not_to be_nil - - # try only to fake the LDAP call - adapter = double('adapter', bind_as: nil).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) - - expect(described_class.login(login, password)).to be_falsey - end - - it "fails if ldap is disabled" do - allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(false) - expect(described_class.login(login, password)).to be_falsey - end - - it "fails if no login is supplied" do - expect(described_class.login('', password)).to be_falsey - end - - it "fails if no password is supplied" do - expect(described_class.login(login, '')).to be_falsey - end - end -end diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb deleted file mode 100644 index e10837578a8..00000000000 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ /dev/null @@ -1,373 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Config do - include LdapHelpers - - let(:config) { described_class.new('ldapmain') } - - describe '.servers' do - it 'returns empty array if no server information is available' do - allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false) - - expect(described_class.servers).to eq [] - end - end - - describe '#initialize' do - it 'requires a provider' do - expect { described_class.new }.to raise_error ArgumentError - end - - it 'works' do - expect(config).to be_a described_class - end - - it 'raises an error if a unknown provider is used' do - expect { described_class.new 'unknown' }.to raise_error(RuntimeError) - end - end - - describe '#adapter_options' do - it 'constructs basic options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 386, - 'encryption' => 'plain' - } - ) - - expect(config.adapter_options).to eq( - host: 'ldap.example.com', - port: 386, - encryption: nil - ) - end - - it 'includes authentication options when auth is configured' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true, - 'bind_dn' => 'uid=admin,dc=example,dc=com', - 'password' => 'super_secret' - } - ) - - expect(config.adapter_options).to include({ - auth: { - method: :simple, - username: 'uid=admin,dc=example,dc=com', - password: 'super_secret' - } - }) - end - - it 'sets encryption method to simple_tls when configured as simple_tls' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls' - } - ) - - expect(config.adapter_options[:encryption]).to include({ method: :simple_tls }) - end - - it 'sets encryption method to start_tls when configured as start_tls' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'start_tls' - } - ) - - expect(config.adapter_options[:encryption]).to include({ method: :start_tls }) - end - - context 'when verify_certificates is enabled' do - it 'sets tls_options to OpenSSL defaults' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true - } - ) - - expect(config.adapter_options[:encryption]).to include({ tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS }) - end - end - - context 'when verify_certificates is disabled' do - it 'sets verify_mode to OpenSSL VERIFY_NONE' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => false - } - ) - - expect(config.adapter_options[:encryption]).to include({ - tls_options: { - verify_mode: OpenSSL::SSL::VERIFY_NONE - } - }) - end - end - - context 'when ca_file is specified' do - it 'passes it through in tls_options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'ca_file' => '/etc/ca.pem' - } - ) - - expect(config.adapter_options[:encryption][:tls_options]).to include({ ca_file: '/etc/ca.pem' }) - end - end - - context 'when ca_file is a blank string' do - it 'does not add the ca_file key to tls_options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'ca_file' => ' ' - } - ) - - expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ca_file) - end - end - - context 'when ssl_version is specified' do - it 'passes it through in tls_options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'ssl_version' => 'TLSv1_2' - } - ) - - expect(config.adapter_options[:encryption][:tls_options]).to include({ ssl_version: 'TLSv1_2' }) - end - end - - context 'when ssl_version is a blank string' do - it 'does not add the ssl_version key to tls_options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'ssl_version' => ' ' - } - ) - - expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ssl_version) - end - end - end - - describe '#omniauth_options' do - it 'constructs basic options' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 386, - 'base' => 'ou=users,dc=example,dc=com', - 'encryption' => 'plain', - 'uid' => 'uid' - } - ) - - expect(config.omniauth_options).to include( - host: 'ldap.example.com', - port: 386, - base: 'ou=users,dc=example,dc=com', - encryption: 'plain', - filter: '(uid=%{username})' - ) - expect(config.omniauth_options.keys).not_to include(:bind_dn, :password) - end - - it 'includes authentication options when auth is configured' do - stub_ldap_config( - options: { - 'uid' => 'sAMAccountName', - 'user_filter' => '(memberOf=cn=group1,ou=groups,dc=example,dc=com)', - 'bind_dn' => 'uid=admin,dc=example,dc=com', - 'password' => 'super_secret' - } - ) - - expect(config.omniauth_options).to include( - filter: '(&(sAMAccountName=%{username})(memberOf=cn=group1,ou=groups,dc=example,dc=com))', - bind_dn: 'uid=admin,dc=example,dc=com', - password: 'super_secret' - ) - end - - context 'when verify_certificates is enabled' do - it 'specifies disable_verify_certificates as false' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true - } - ) - - expect(config.omniauth_options).to include({ disable_verify_certificates: false }) - end - end - - context 'when verify_certificates is disabled' do - it 'specifies disable_verify_certificates as true' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => false - } - ) - - expect(config.omniauth_options).to include({ disable_verify_certificates: true }) - end - end - - context 'when ca_file is present' do - it 'passes it through' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true, - 'ca_file' => '/etc/ca.pem' - } - ) - - expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' }) - end - end - - context 'when ca_file is blank' do - it 'does not include the ca_file option' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true, - 'ca_file' => ' ' - } - ) - - expect(config.omniauth_options).not_to have_key(:ca_file) - end - end - - context 'when ssl_version is present' do - it 'passes it through' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true, - 'ssl_version' => 'TLSv1_2' - } - ) - - expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' }) - end - end - - context 'when ssl_version is blank' do - it 'does not include the ssl_version option' do - stub_ldap_config( - options: { - 'host' => 'ldap.example.com', - 'port' => 686, - 'encryption' => 'simple_tls', - 'verify_certificates' => true, - 'ssl_version' => ' ' - } - ) - - expect(config.omniauth_options).not_to have_key(:ssl_version) - end - end - end - - describe '#has_auth?' do - it 'is true when password is set' do - stub_ldap_config( - options: { - 'bind_dn' => 'uid=admin,dc=example,dc=com', - 'password' => 'super_secret' - } - ) - - expect(config.has_auth?).to be_truthy - end - - it 'is true when bind_dn is set and password is empty' do - stub_ldap_config( - options: { - 'bind_dn' => 'uid=admin,dc=example,dc=com', - 'password' => '' - } - ) - - expect(config.has_auth?).to be_truthy - end - - it 'is false when password and bind_dn are not set' do - stub_ldap_config(options: { 'bind_dn' => nil, 'password' => nil }) - - expect(config.has_auth?).to be_falsey - end - end - - describe '#attributes' do - it 'uses default attributes when no custom attributes are configured' do - expect(config.attributes).to eq(config.default_attributes) - end - - it 'merges the configuration attributes with default attributes' do - stub_ldap_config( - options: { - 'attributes' => { - 'username' => %w(sAMAccountName), - 'email' => %w(userPrincipalName) - } - } - ) - - expect(config.attributes).to include({ - 'username' => %w(sAMAccountName), - 'email' => %w(userPrincipalName), - 'name' => 'cn' - }) - end - end -end diff --git a/spec/lib/gitlab/ldap/dn_spec.rb b/spec/lib/gitlab/ldap/dn_spec.rb deleted file mode 100644 index 8e21ecdf9ab..00000000000 --- a/spec/lib/gitlab/ldap/dn_spec.rb +++ /dev/null @@ -1,224 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::DN do - using RSpec::Parameterized::TableSyntax - - describe '#normalize_value' do - subject { described_class.normalize_value(given) } - - it_behaves_like 'normalizes a DN attribute value' - - context 'when the given DN is malformed' do - context 'when ending with a comma' do - let(:given) { 'John Smith,' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'when given a BER encoded attribute value with a space in it' do - let(:given) { '#aa aa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") - end - end - - context 'when given a BER encoded attribute value with a non-hex character in it' do - let(:given) { '#aaXaaa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") - end - end - - context 'when given a BER encoded attribute value with a non-hex character in it' do - let(:given) { '#aaaYaa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") - end - end - - context 'when given a hex pair with a non-hex character in it, inside double quotes' do - let(:given) { '"Sebasti\\cX\\a1n"' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") - end - end - - context 'with an open (as opposed to closed) double quote' do - let(:given) { '"James' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'with an invalid escaped hex code' do - let(:given) { 'J\ames' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') - end - end - - context 'with a value ending with the escape character' do - let(:given) { 'foo\\' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - end - end - - describe '#to_normalized_s' do - subject { described_class.new(given).to_normalized_s } - - it_behaves_like 'normalizes a DN' - - context 'when we do not support the given DN format' do - context 'multivalued RDNs' do - context 'without extraneous whitespace' do - let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' } - - it 'raises UnsupportedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) - end - end - - context 'with extraneous whitespace' do - context 'around the phone number plus sign' do - let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' } - - it 'raises UnsupportedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) - end - end - - context 'not around the phone number plus sign' do - let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' } - - it 'raises UnsupportedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) - end - end - end - end - end - - context 'when the given DN is malformed' do - context 'when ending with a comma' do - let(:given) { 'uid=John Smith,' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'when given a BER encoded attribute value with a space in it' do - let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") - end - end - - context 'when given a BER encoded attribute value with a non-hex character in it' do - let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") - end - end - - context 'when given a BER encoded attribute value with a non-hex character in it' do - let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") - end - end - - context 'when given a hex pair with a non-hex character in it, inside double quotes' do - let(:given) { 'uid="Sebasti\\cX\\a1n"' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") - end - end - - context 'without a name value pair' do - let(:given) { 'John' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'with an open (as opposed to closed) double quote' do - let(:given) { 'cn="James' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'with an invalid escaped hex code' do - let(:given) { 'cn=J\ames' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') - end - end - - context 'with a value ending with the escape character' do - let(:given) { 'cn=\\' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') - end - end - - context 'with an invalid OID attribute type name' do - let(:given) { '1.2.d=Value' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"') - end - end - - context 'with a period in a non-OID attribute type name' do - let(:given) { 'd1.2=Value' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."') - end - end - - context 'when starting with non-space, non-alphanumeric character' do - let(:given) { ' -uid=John Smith' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"') - end - end - - context 'when given a UID with an escaped equal sign' do - let(:given) { 'uid\\=john' } - - it 'raises MalformedError' do - expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"') - end - end - end - end - - def assert_generic_test(test_description, got, expected) - test_failure_message = "Failed test description: '#{test_description}'\n\n expected: \"#{expected}\"\n got: \"#{got}\"" - expect(got).to eq(expected), test_failure_message - end -end diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb deleted file mode 100644 index 05e1e394bb1..00000000000 --- a/spec/lib/gitlab/ldap/person_spec.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Person do - include LdapHelpers - - let(:entry) { ldap_user_entry('john.doe') } - - before do - stub_ldap_config( - options: { - 'uid' => 'uid', - 'attributes' => { - 'name' => 'cn', - 'email' => %w(mail email userPrincipalName), - 'username' => username_attribute - } - } - ) - end - let(:username_attribute) { %w(uid sAMAccountName userid) } - - describe '.normalize_dn' do - subject { described_class.normalize_dn(given) } - - it_behaves_like 'normalizes a DN' - - context 'with an exception during normalization' do - let(:given) { 'John "Smith,' } # just something that will cause an exception - - it 'returns the given DN unmodified' do - expect(subject).to eq(given) - end - end - end - - describe '.normalize_uid' do - subject { described_class.normalize_uid(given) } - - it_behaves_like 'normalizes a DN attribute value' - - context 'with an exception during normalization' do - let(:given) { 'John "Smith,' } # just something that will cause an exception - - it 'returns the given UID unmodified' do - expect(subject).to eq(given) - end - end - end - - describe '.ldap_attributes' do - it 'returns a compact and unique array' do - stub_ldap_config( - options: { - 'uid' => nil, - 'attributes' => { - 'name' => 'cn', - 'email' => 'mail', - 'username' => %w(uid mail memberof) - } - } - ) - config = Gitlab::LDAP::Config.new('ldapmain') - ldap_attributes = described_class.ldap_attributes(config) - - expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof)) - end - end - - describe '#name' do - it 'uses the configured name attribute and handles values as an array' do - name = 'John Doe' - entry['cn'] = [name] - person = described_class.new(entry, 'ldapmain') - - expect(person.name).to eq(name) - end - end - - describe '#email' do - it 'returns the value of mail, if present' do - mail = 'john@example.com' - entry['mail'] = mail - person = described_class.new(entry, 'ldapmain') - - expect(person.email).to eq([mail]) - end - - it 'returns the value of userPrincipalName, if mail and email are not present' do - user_principal_name = 'john.doe@example.com' - entry['userPrincipalName'] = user_principal_name - person = described_class.new(entry, 'ldapmain') - - expect(person.email).to eq([user_principal_name]) - end - end - - describe '#username' do - context 'with default uid username attribute' do - let(:username_attribute) { 'uid' } - - it 'returns the proper username value' do - attr_value = 'johndoe' - entry[username_attribute] = attr_value - person = described_class.new(entry, 'ldapmain') - - expect(person.username).to eq(attr_value) - end - end - - context 'with a different username attribute' do - let(:username_attribute) { 'sAMAccountName' } - - it 'returns the proper username value' do - attr_value = 'johndoe' - entry[username_attribute] = attr_value - person = described_class.new(entry, 'ldapmain') - - expect(person.username).to eq(attr_value) - end - end - - context 'with a non-standard username attribute' do - let(:username_attribute) { 'mail' } - - it 'returns the proper username value' do - attr_value = 'john.doe@example.com' - entry[username_attribute] = attr_value - person = described_class.new(entry, 'ldapmain') - - expect(person.username).to eq(attr_value) - end - end - - context 'if lowercase_usernames setting is' do - let(:username_attribute) { 'uid' } - - before do - entry[username_attribute] = 'JOHN' - @person = described_class.new(entry, 'ldapmain') - end - - it 'enabled the username attribute is lower cased' do - stub_ldap_config(lowercase_usernames: true) - - expect(@person.username).to eq 'john' - end - - it 'disabled the username attribute is not lower cased' do - stub_ldap_config(lowercase_usernames: false) - - expect(@person.username).to eq 'JOHN' - end - end - end - - def assert_generic_test(test_description, got, expected) - test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}" - expect(got).to eq(expected), test_failure_message - end -end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb deleted file mode 100644 index 048caa38fcf..00000000000 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ /dev/null @@ -1,241 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::User do - let(:ldap_user) { described_class.new(auth_hash) } - let(:gl_user) { ldap_user.gl_user } - let(:info) do - { - name: 'John', - email: 'john@example.com', - nickname: 'john' - } - end - let(:auth_hash) do - OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info) - end - let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) } - let(:info_upper_case) do - { - name: 'John', - email: 'John@Example.com', # Email address has upper case chars - nickname: 'john' - } - end - let(:auth_hash_upper_case) do - OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info_upper_case) - end - - describe '#changed?' do - it "marks existing ldap user as changed" do - create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain') - expect(ldap_user.changed?).to be_truthy - end - - it "marks existing non-ldap user if the email matches as changed" do - create(:user, email: 'john@example.com') - expect(ldap_user.changed?).to be_truthy - end - - it "does not mark existing ldap user as changed" do - create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') - expect(ldap_user.changed?).to be_falsey - end - end - - describe '.find_by_uid_and_provider' do - let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' } - - it 'retrieves the correct user' do - special_info = { - name: 'John Åström', - email: 'john@example.com', - nickname: 'jastrom' - } - special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info) - special_chars_user = described_class.new(special_hash) - user = special_chars_user.save - - expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user - end - end - - describe 'find or create' do - it "finds the user if already existing" do - create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') - - expect { ldap_user.save }.not_to change { User.count } - end - - it "connects to existing non-ldap user if the email matches" do - existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") - expect { ldap_user.save }.not_to change { User.count } - - existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' - expect(existing_user.ldap_identity.provider).to eql 'ldapmain' - end - - it 'connects to existing ldap user if the extern_uid changes' do - existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect { ldap_user.save }.not_to change { User.count } - - existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' - expect(existing_user.ldap_identity.provider).to eql 'ldapmain' - expect(existing_user.id).to eql ldap_user.gl_user.id - end - - it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do - existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect { ldap_user_upper_case.save }.not_to change { User.count } - - existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' - expect(existing_user.ldap_identity.provider).to eql 'ldapmain' - expect(existing_user.id).to eql ldap_user.gl_user.id - end - - it 'maintains an identity per provider' do - existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter') - expect(existing_user.identities.count).to be(1) - - ldap_user.save - expect(ldap_user.gl_user.identities.count).to be(2) - - # Expect that find_by provider only returns a single instance of an identity and not an Enumerable - expect(ldap_user.gl_user.identities.find_by(provider: 'twitter')).to be_instance_of Identity - expect(ldap_user.gl_user.identities.find_by(provider: auth_hash.provider)).to be_instance_of Identity - end - - it "creates a new user if not found" do - expect { ldap_user.save }.to change { User.count }.by(1) - end - - context 'when signup is disabled' do - before do - stub_application_setting signup_enabled: false - end - - it 'creates the user' do - ldap_user.save - - expect(gl_user).to be_persisted - end - end - - context 'when user confirmation email is enabled' do - before do - stub_application_setting send_user_confirmation_email: true - end - - it 'creates and confirms the user anyway' do - ldap_user.save - - expect(gl_user).to be_persisted - expect(gl_user).to be_confirmed - end - end - end - - describe 'updating email' do - context "when LDAP sets an email" do - it "has a real email" do - expect(ldap_user.gl_user.email).to eq(info[:email]) - end - - it "has email set as synced" do - expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_truthy - end - - it "has email set as read-only" do - expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_truthy - end - - it "has synced attributes provider set to ldapmain" do - expect(ldap_user.gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' - end - end - - context "when LDAP doesn't set an email" do - before do - info.delete(:email) - end - - it "has a temp email" do - expect(ldap_user.gl_user.temp_oauth_email?).to be_truthy - end - - it "has email set as not synced" do - expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_falsey - end - - it "does not have email set as read-only" do - expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_falsey - end - end - end - - describe 'blocking' do - def configure_block(value) - allow_any_instance_of(Gitlab::LDAP::Config) - .to receive(:block_auto_created_users).and_return(value) - end - - context 'signup' do - context 'dont block on create' do - before do - configure_block(false) - end - - it do - ldap_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - configure_block(true) - end - - it do - ldap_user.save - expect(gl_user).to be_valid - expect(gl_user).to be_blocked - end - end - end - - context 'sign-in' do - before do - ldap_user.save - ldap_user.gl_user.activate - end - - context 'dont block on create' do - before do - configure_block(false) - end - - it do - ldap_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - configure_block(true) - end - - it do - ldap_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - end - end -end diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb deleted file mode 100644 index dbcc200b90b..00000000000 --- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb +++ /dev/null @@ -1,111 +0,0 @@ -require 'spec_helper' - -describe Gitlab::OAuth::AuthHash do - let(:provider) { 'ldap'.freeze } - let(:auth_hash) do - described_class.new( - OmniAuth::AuthHash.new( - provider: provider, - uid: uid_ascii, - info: info_hash - ) - ) - end - - let(:uid_raw) do - "CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net" - end - let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" } - let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" } - let(:first_name_raw) { 'Onur' } - let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" } - let(:name_raw) { "Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" } - - let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) } - let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) } - let(:nickname_ascii) { nickname_raw.force_encoding(Encoding::ASCII_8BIT) } - let(:first_name_ascii) { first_name_raw.force_encoding(Encoding::ASCII_8BIT) } - let(:last_name_ascii) { last_name_raw.force_encoding(Encoding::ASCII_8BIT) } - let(:name_ascii) { name_raw.force_encoding(Encoding::ASCII_8BIT) } - - let(:uid_utf8) { uid_ascii.force_encoding(Encoding::UTF_8) } - 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(:info_hash) do - { - email: email_ascii, - first_name: first_name_ascii, - last_name: last_name_ascii, - name: name_ascii, - nickname: nickname_ascii, - uid: uid_ascii - } - end - - context 'defaults' do - it { expect(auth_hash.provider).to eq provider } - it { expect(auth_hash.uid).to eql uid_utf8 } - it { expect(auth_hash.email).to eql email_utf8 } - it { expect(auth_hash.username).to eql nickname_utf8 } - it { expect(auth_hash.name).to eql name_utf8 } - it { expect(auth_hash.password).not_to be_empty } - end - - context 'email not provided' do - before do - info_hash.delete(:email) - end - - it 'generates a temp email' do - expect( auth_hash.email).to start_with('temp-email-for-oauth') - end - end - - context 'username not provided' do - before do - info_hash.delete(:nickname) - end - - it 'takes the first part of the email as username' do - expect(auth_hash.username).to eql 'onur.kucuk_ABC-123' - end - end - - context 'name not provided' do - before do - info_hash.delete(:name) - end - - it 'concats first and lastname as the name' do - expect(auth_hash.name).to eql name_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 - end - - it 'forces utf8 encoding on provider' do - expect(auth_hash.provider.encoding).to eql Encoding::UTF_8 - end - - it 'forces utf8 encoding on name' do - expect(auth_hash.name.encoding).to eql Encoding::UTF_8 - end - - it 'forces utf8 encoding on username' do - expect(auth_hash.username.encoding).to eql Encoding::UTF_8 - end - - it 'forces utf8 encoding on email' do - expect(auth_hash.email.encoding).to eql Encoding::UTF_8 - end - - it 'forces utf8 encoding on password' do - expect(auth_hash.password.encoding).to eql Encoding::UTF_8 - end - end -end diff --git a/spec/lib/gitlab/o_auth/provider_spec.rb b/spec/lib/gitlab/o_auth/provider_spec.rb deleted file mode 100644 index 30faf107e3f..00000000000 --- a/spec/lib/gitlab/o_auth/provider_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'spec_helper' - -describe Gitlab::OAuth::Provider do - describe '#config_for' do - context 'for an LDAP provider' do - context 'when the provider exists' do - it 'returns the config' do - expect(described_class.config_for('ldapmain')).to be_a(Hash) - end - end - - context 'when the provider does not exist' do - it 'returns nil' do - expect(described_class.config_for('ldapfoo')).to be_nil - end - end - end - - context 'for an OmniAuth provider' do - before do - provider = OpenStruct.new( - name: 'google', - app_id: 'asd123', - app_secret: 'asd123' - ) - allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider]) - end - - context 'when the provider exists' do - it 'returns the config' do - expect(described_class.config_for('google')).to be_a(OpenStruct) - end - end - - context 'when the provider does not exist' do - it 'returns nil' do - expect(described_class.config_for('foo')).to be_nil - end - end - end - end -end diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb deleted file mode 100644 index b8455403bdb..00000000000 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ /dev/null @@ -1,742 +0,0 @@ -require 'spec_helper' - -describe Gitlab::OAuth::User do - let(:oauth_user) { described_class.new(auth_hash) } - let(:gl_user) { oauth_user.gl_user } - let(:uid) { 'my-uid' } - let(:dn) { 'uid=user1,ou=people,dc=example' } - let(:provider) { 'my-provider' } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) } - let(:info_hash) do - { - nickname: '-john+gitlab-ETC%.git@gmail.com', - name: 'John', - email: 'john@mail.com', - address: { - locality: 'locality', - country: 'country' - } - } - 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') } - - it "finds an existing user based on uid and provider (facebook)" do - expect( oauth_user.persisted? ).to be_truthy - end - - it 'returns false if user is not found in database' do - allow(auth_hash).to receive(:uid).and_return('non-existing') - expect( oauth_user.persisted? ).to be_falsey - end - end - - def stub_omniauth_config(messages) - allow(Gitlab.config.omniauth).to receive_messages(messages) - end - - describe '#save' do - def stub_ldap_config(messages) - allow(Gitlab::LDAP::Config).to receive_messages(messages) - end - - let(:provider) { 'twitter' } - - describe 'when account exists on server' do - it 'does not mark the user as external' do - create(:omniauth_user, extern_uid: 'my-uid', provider: provider) - stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - - oauth_user.save - - expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey - end - end - - describe 'signup' do - context 'when signup is disabled' do - before do - stub_application_setting signup_enabled: false - end - - it 'creates the user' do - stub_omniauth_config(allow_single_sign_on: [provider]) - - oauth_user.save - - expect(gl_user).to be_persisted - end - end - - context 'when user confirmation email is enabled' do - before do - stub_application_setting send_user_confirmation_email: true - end - - it 'creates and confirms the user anyway' do - stub_omniauth_config(allow_single_sign_on: [provider]) - - oauth_user.save - - expect(gl_user).to be_persisted - expect(gl_user).to be_confirmed - end - end - - it 'marks user as having password_automatically_set' do - stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - - oauth_user.save - - expect(gl_user).to be_persisted - expect(gl_user).to be_password_automatically_set - end - - shared_examples 'to verify compliance with allow_single_sign_on' do - context 'provider is marked as external' do - it 'marks user as external' do - stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_truthy - end - end - - context 'provider was external, now has been removed' do - it 'does not mark external user as internal' do - create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true) - stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook']) - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_truthy - end - end - - context 'provider is not external' do - context 'when adding a new OAuth identity' do - it 'does not promote an external user to internal' do - user = create(:user, email: 'john@mail.com', external: true) - user.identities.create(provider: provider, extern_uid: uid) - - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_truthy - end - end - end - - context 'with new allow_single_sign_on enabled syntax' do - before do - stub_omniauth_config(allow_single_sign_on: [provider]) - end - - 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 provider - end - end - - context "with old allow_single_sign_on enabled syntax" do - before do - stub_omniauth_config(allow_single_sign_on: true) - end - - 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 provider - end - end - - context 'with new allow_single_sign_on disabled syntax' do - before do - stub_omniauth_config(allow_single_sign_on: []) - end - - it 'throws an error' do - expect { oauth_user.save }.to raise_error StandardError - end - end - - context 'with old allow_single_sign_on disabled (Default)' do - before do - stub_omniauth_config(allow_single_sign_on: false) - end - - it 'throws an error' do - expect { oauth_user.save }.to raise_error StandardError - end - end - end - - context "with auto_link_ldap_user disabled (default)" do - before do - stub_omniauth_config(auto_link_ldap_user: false) - end - - include_examples "to verify compliance with allow_single_sign_on" - end - - context "with auto_link_ldap_user enabled" do - before do - stub_omniauth_config(auto_link_ldap_user: true) - end - - context "and no LDAP provider defined" do - before do - stub_ldap_config(providers: []) - end - - include_examples "to verify compliance with allow_single_sign_on" - end - - context "and at least one LDAP provider is defined" do - before do - stub_ldap_config(providers: %w(ldapmain)) - end - - context "and a corresponding LDAP person" do - before do - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } - allow(ldap_user).to receive(:dn) { dn } - end - - context "and no account for the LDAP user" do - before do - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) - - oauth_user.save - end - - it "creates a user with dual LDAP and omniauth identities" do - 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 be 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: dn }, - { provider: 'twitter', extern_uid: uid } - ] - ) - end - - it "has email set as synced" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy - end - - it "has email set as read-only" do - expect(gl_user.read_only_attribute?(:email)).to be_truthy - end - - it "has synced attributes provider set to ldapmain" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' - end - end - - context "and LDAP user has an account already" do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } - it "adds the omniauth identity to the LDAP account" do - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) - - 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 be 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: dn }, - { provider: 'twitter', extern_uid: uid } - ] - ) - end - end - - context 'when an LDAP person is not found by uid' do - it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) - - oauth_user.save - - 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: dn }, - { provider: 'twitter', extern_uid: uid } - ] - ) - end - end - end - - context 'and a corresponding LDAP person with a non-default username' do - before do - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { 'johndoe@example.com' } - allow(ldap_user).to receive(:email) { %w(johndoe@example.com john2@example.com) } - allow(ldap_user).to receive(:dn) { dn } - end - - context 'and no account for the LDAP user' do - it 'creates a user favoring the LDAP username and strips email domain' do - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) - - oauth_user.save - - expect(gl_user).to be_valid - expect(gl_user.username).to eql 'johndoe' - end - end - end - - context "and no corresponding LDAP person" do - before do - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) - end - - include_examples "to verify compliance with allow_single_sign_on" - end - end - end - end - - describe 'blocking' do - let(:provider) { 'twitter' } - - before do - stub_omniauth_config(allow_single_sign_on: ['twitter']) - end - - context 'signup with omniauth only' do - context 'dont block on create' do - before do - stub_omniauth_config(block_auto_created_users: false) - end - - it do - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - stub_omniauth_config(block_auto_created_users: true) - end - - it do - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user).to be_blocked - end - end - end - - context 'signup with linked omniauth and LDAP account' do - before do - stub_omniauth_config(auto_link_ldap_user: true) - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } - allow(ldap_user).to receive(:dn) { dn } - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) - end - - context "and no account for the LDAP user" do - context 'dont block on create (LDAP)' do - before do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) - end - - 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 do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) - end - - 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: dn, provider: 'ldapmain', username: 'john') } - - context 'dont block on create (LDAP)' do - before do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) - end - - 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 do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) - end - - 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 - oauth_user.gl_user.activate - end - - context 'dont block on create' do - before do - stub_omniauth_config(block_auto_created_users: false) - end - - it do - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - stub_omniauth_config(block_auto_created_users: true) - end - - it do - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'dont block on create (LDAP)' do - before do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) - end - - 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 do - allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) - end - - it do - oauth_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - end - end - end - - describe 'ensure backwards compatibility with with sync email from provider option' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - before do - stub_omniauth_config(sync_email_from_provider: 'my-provider') - stub_omniauth_config(sync_profile_from_provider: ['my-provider']) - end - - context "when provider sets an email" do - it "updates the user email" do - expect(gl_user.email).to eq(info_hash[:email]) - end - - it "has email set as synced" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy - end - - it "has email set as read-only" do - expect(gl_user.read_only_attribute?(:email)).to be_truthy - end - - it "has synced attributes provider set to my-provider" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' - end - end - - context "when provider doesn't set an email" do - before do - info_hash.delete(:email) - end - - it "does not update the user email" do - expect(gl_user.email).not_to eq(info_hash[:email]) - end - - it "has email set as not synced" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey - end - - it "does not have email set as read-only" do - expect(gl_user.read_only_attribute?(:email)).to be_falsey - end - end - end - - describe 'generating username' do - context 'when no collision with existing user' do - it 'generates the username with no counter' do - expect(gl_user.username).to eq('johngitlab-ETC') - end - end - - context 'when collision with existing user' do - it 'generates the username with a counter' do - oauth_user.save - oauth_user2 = described_class.new(OmniAuth::AuthHash.new(uid: 'my-uid2', provider: provider, info: { nickname: 'johngitlab-ETC@othermail.com', email: 'john@othermail.com' })) - - expect(oauth_user2.gl_user.username).to eq('johngitlab-ETC1') - end - end - - context 'when username is a reserved word' do - let(:info_hash) do - { - nickname: 'admin@othermail.com', - email: 'admin@othermail.com' - } - end - - it 'generates the username with a counter' do - expect(gl_user.username).to eq('admin1') - end - end - end - - describe 'updating email with sync profile' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - before do - stub_omniauth_config(sync_profile_from_provider: ['my-provider']) - stub_omniauth_config(sync_profile_attributes: true) - end - - context "when provider sets an email" do - it "updates the user email" do - expect(gl_user.email).to eq(info_hash[:email]) - end - - it "has email set as synced" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) - end - - it "has email set as read-only" do - expect(gl_user.read_only_attribute?(:email)).to be_truthy - end - - it "has synced attributes provider set to my-provider" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' - end - end - - context "when provider doesn't set an email" do - before do - info_hash.delete(:email) - end - - it "does not update the user email" do - expect(gl_user.email).not_to eq(info_hash[:email]) - end - - it "has email set as not synced" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey - end - - it "does not have email set as read-only" do - expect(gl_user.read_only_attribute?(:email)).to be_falsey - end - end - end - - describe 'updating name' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - before do - stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: true) - end - - context "when provider sets a name" do - it "updates the user name" do - expect(gl_user.name).to eq(info_hash[:name]) - end - end - - context "when provider doesn't set a name" do - before do - info_hash.delete(:name) - end - - it "does not update the user name" do - expect(gl_user.name).not_to eq(info_hash[:name]) - expect(gl_user.user_synced_attributes_metadata.name_synced).to be(false) - end - end - end - - describe 'updating location' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - before do - stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: true) - end - - context "when provider sets a location" do - it "updates the user location" do - expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) - expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) - end - end - - context "when provider doesn't set a location" do - before do - info_hash[:address].delete(:country) - info_hash[:address].delete(:locality) - end - - it "does not update the user location" do - expect(gl_user.location).to be_nil - expect(gl_user.user_synced_attributes_metadata.location_synced).to be(false) - end - end - end - - describe 'updating user info' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - context "update all info" do - before do - stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: true) - end - - it "updates the user email" do - expect(gl_user.email).to eq(info_hash[:email]) - expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) - end - - it "updates the user name" do - expect(gl_user.name).to eq(info_hash[:name]) - expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true) - end - - it "updates the user location" do - expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) - expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) - end - - it "sets my-provider as the attributes provider" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql('my-provider') - end - end - - context "update only requested info" do - before do - stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: %w(name location)) - end - - it "updates the user name" do - expect(gl_user.name).to eq(info_hash[:name]) - expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true) - end - - it "updates the user location" do - expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country]) - expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true) - end - - it "does not update the user email" do - expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false) - end - end - - context "update default_scope" do - before do - stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - end - - it "updates the user email" do - expect(gl_user.email).to eq(info_hash[:email]) - expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) - end - end - - context "update no info when profile sync is nil" do - it "does not have sync_attribute" do - expect(gl_user.user_synced_attributes_metadata).to be(nil) - end - - it "does not update the user email" do - expect(gl_user.email).not_to eq(info_hash[:email]) - end - - it "does not update the user name" do - expect(gl_user.name).not_to eq(info_hash[:name]) - end - - it "does not update the user location" do - expect(gl_user.location).not_to eq(info_hash[:address][:country]) - end - - it 'does not create associated user synced attributes metadata' do - expect(gl_user.user_synced_attributes_metadata).to be_nil - end - end - end - - describe '.find_by_uid_and_provider' do - let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } - - it 'normalizes extern_uid' do - allow(oauth_user.auth_hash).to receive(:uid).and_return('MY-UID') - expect(oauth_user.find_user).to eql gl_user - end - end -end diff --git a/spec/lib/gitlab/saml/auth_hash_spec.rb b/spec/lib/gitlab/saml/auth_hash_spec.rb deleted file mode 100644 index a555935aea3..00000000000 --- a/spec/lib/gitlab/saml/auth_hash_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Saml::AuthHash do - include LoginHelpers - - let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } } - subject(:saml_auth_hash) { described_class.new(omniauth_auth_hash) } - - let(:info_hash) do - { - name: 'John', - email: 'john@mail.com' - } - end - - let(:omniauth_auth_hash) do - OmniAuth::AuthHash.new(uid: 'my-uid', - provider: 'saml', - info: info_hash, - extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) } ) - end - - before do - stub_saml_group_config(%w(Developers Freelancers Designers)) - end - - describe '#groups' do - it 'returns array of groups' do - expect(saml_auth_hash.groups).to eq(%w(Developers Freelancers)) - end - - context 'raw info hash attributes empty' do - let(:raw_info_attr) { {} } - - it 'returns an empty array' do - expect(saml_auth_hash.groups).to be_a(Array) - end - end - end -end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb deleted file mode 100644 index 1765980e977..00000000000 --- a/spec/lib/gitlab/saml/user_spec.rb +++ /dev/null @@ -1,403 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Saml::User do - include LdapHelpers - include LoginHelpers - - let(:saml_user) { described_class.new(auth_hash) } - let(:gl_user) { saml_user.gl_user } - let(:uid) { 'my-uid' } - let(:dn) { 'uid=user1,ou=people,dc=example' } - let(:provider) { 'saml' } - let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers Designers) } } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) }) } - let(:info_hash) do - { - name: 'John', - email: 'john@mail.com' - } - end - let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } - - describe '#save' do - before do - stub_basic_saml_config - end - - describe 'account exists on server' do - before do - stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) - end - - let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } - - context 'and should bind with SAML' do - it 'adds the SAML identity to the existing user' do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).to eq existing_user - identity = gl_user.identities.first - expect(identity.extern_uid).to eql uid - expect(identity.provider).to eql 'saml' - end - end - - context 'external groups' do - context 'are defined' do - it 'marks the user as external' do - stub_saml_group_config(%w(Freelancers)) - saml_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_truthy - end - end - - before do - stub_saml_group_config(%w(Interns)) - end - - context 'are defined but the user does not belong there' do - it 'does not mark the user as external' do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey - end - end - - context 'user was external, now should not be' do - it 'makes user internal' do - existing_user.update_attribute('external', true) - saml_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey - end - end - end - end - - describe 'no account exists on server' do - shared_examples 'to verify compliance with allow_single_sign_on' do - context 'with allow_single_sign_on enabled' do - before do - stub_omniauth_config(allow_single_sign_on: ['saml']) - end - - it 'creates a user from SAML' do - saml_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 'saml' - end - end - - context 'with allow_single_sign_on default (["saml"])' do - before do - stub_omniauth_config(allow_single_sign_on: ['saml']) - end - - it 'does not throw an error' do - expect { saml_user.save }.not_to raise_error - end - end - - context 'with allow_single_sign_on disabled' do - before do - stub_omniauth_config(allow_single_sign_on: false) - end - - it 'throws an error' do - expect { saml_user.save }.to raise_error StandardError - end - end - end - - context 'external groups' do - context 'are defined' do - it 'marks the user as external' do - stub_saml_group_config(%w(Freelancers)) - saml_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_truthy - end - end - - context 'are defined but the user does not belong there' do - it 'does not mark the user as external' do - stub_saml_group_config(%w(Interns)) - saml_user.save - expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey - end - end - end - - context 'with auto_link_ldap_user disabled (default)' do - before do - stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) - end - - include_examples 'to verify compliance with allow_single_sign_on' - end - - context 'with auto_link_ldap_user enabled' do - before do - stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) - end - - context 'and at least one LDAP provider is defined' do - before do - stub_ldap_config(providers: %w(ldapmain)) - end - - context 'and a corresponding LDAP person' do - let(:adapter) { ldap_adapter('ldapmain') } - - before do - allow(ldap_user).to receive(:uid) { uid } - allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } - allow(ldap_user).to receive(:dn) { dn } - allow(Gitlab::LDAP::Adapter).to receive(:new).and_return(adapter) - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user) - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).with(dn, adapter).and_return(ldap_user) - allow(Gitlab::LDAP::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user) - end - - context 'and no account for the LDAP user' do - it 'creates a user with dual LDAP and SAML identities' do - saml_user.save - - expect(gl_user).to be_valid - expect(gl_user.username).to eql uid - expect(gl_user.email).to eql 'john@mail.com' - expect(gl_user.identities.length).to be 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: dn }, - { provider: 'saml', extern_uid: uid }]) - end - end - - context 'and LDAP user has an account already' do - let(:auth_hash_base_attributes) do - { - uid: uid, - provider: provider, - info: info_hash, - extra: { - raw_info: OneLogin::RubySaml::Attributes.new( - { 'groups' => %w(Developers Freelancers Designers) } - ) - } - } - end - let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes) } - let(:uid_types) { %w(uid dn email) } - - before do - create(:omniauth_user, - email: 'john@mail.com', - extern_uid: dn, - provider: 'ldapmain', - username: 'john') - end - - shared_examples 'find LDAP person' do |uid_type, uid| - let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes.merge(uid: extern_uid)) } - - before do - nil_types = uid_types - [uid_type] - - nil_types.each do |type| - allow(Gitlab::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil) - end - - allow(Gitlab::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user) - end - - it 'adds the omniauth identity to the LDAP account' do - identities = [ - { provider: 'ldapmain', extern_uid: dn }, - { provider: 'saml', extern_uid: extern_uid } - ] - - identities_as_hash = gl_user.identities.map do |id| - { provider: id.provider, extern_uid: id.extern_uid } - end - - saml_user.save - - expect(gl_user).to be_valid - expect(gl_user.username).to eql 'john' - expect(gl_user.email).to eql 'john@mail.com' - expect(gl_user.identities.length).to be 2 - expect(identities_as_hash).to match_array(identities) - end - end - - context 'when uid is an uid' do - it_behaves_like 'find LDAP person', 'uid' do - let(:extern_uid) { uid } - end - end - - context 'when uid is a dn' do - it_behaves_like 'find LDAP person', 'dn' do - let(:extern_uid) { dn } - end - end - - context 'when uid is an email' do - it_behaves_like 'find LDAP person', 'email' do - let(:extern_uid) { 'john@mail.com' } - end - end - - it 'adds the omniauth identity to the LDAP account' do - saml_user.save - - expect(gl_user).to be_valid - expect(gl_user.username).to eql 'john' - expect(gl_user.email).to eql 'john@mail.com' - expect(gl_user.identities.length).to be 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: dn }, - { provider: 'saml', extern_uid: uid }]) - end - - it 'saves successfully on subsequent tries, when both identities are present' do - saml_user.save - local_saml_user = described_class.new(auth_hash) - local_saml_user.save - - expect(local_saml_user.gl_user).to be_valid - expect(local_saml_user.gl_user).to be_persisted - end - end - - context 'user has SAML user, and wants to add their LDAP identity' do - it 'adds the LDAP identity to the existing SAML user' do - create(:omniauth_user, email: 'john@mail.com', extern_uid: dn, provider: 'saml', username: 'john') - - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).with(dn, adapter).and_return(ldap_user) - - local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash) - local_saml_user = described_class.new(local_hash) - - local_saml_user.save - local_gl_user = local_saml_user.gl_user - - expect(local_gl_user).to be_valid - expect(local_gl_user.identities.length).to be 2 - identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: dn }, - { provider: 'saml', extern_uid: dn }]) - end - end - end - end - end - - context 'when signup is disabled' do - before do - stub_application_setting signup_enabled: false - end - - it 'creates the user' do - saml_user.save - - expect(gl_user).to be_persisted - end - end - - context 'when user confirmation email is enabled' do - before do - stub_application_setting send_user_confirmation_email: true - end - - it 'creates and confirms the user anyway' do - saml_user.save - - expect(gl_user).to be_persisted - expect(gl_user).to be_confirmed - end - end - end - - describe 'blocking' do - before do - stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) - end - - context 'signup with SAML only' do - context 'dont block on create' do - before do - stub_omniauth_config(block_auto_created_users: false) - end - - it 'does not block the user' do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - stub_omniauth_config(block_auto_created_users: true) - end - - it 'blocks user' do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).to be_blocked - end - end - end - - context 'sign-in' do - before do - saml_user.save - saml_user.gl_user.activate - end - - context 'dont block on create' do - before do - stub_omniauth_config(block_auto_created_users: false) - end - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - - context 'block on create' do - before do - stub_omniauth_config(block_auto_created_users: true) - end - - it do - saml_user.save - expect(gl_user).to be_valid - expect(gl_user).not_to be_blocked - end - end - end - end - end - - describe '#find_user' do - context 'raw info hash attributes empty' do - let(:raw_info_attr) { {} } - - it 'does not mark user as external' do - stub_saml_group_config(%w(Freelancers)) - - expect(saml_user.find_user.external).to be_falsy - end - end - end -end -- cgit v1.2.1