summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatricio Cano <suprnova32@gmail.com>2016-02-18 17:01:07 -0500
committerPatricio Cano <suprnova32@gmail.com>2016-02-18 17:01:07 -0500
commit06376be56a84b43976b63aad91638fb2c00fec1a (patch)
tree049b4900ff1700a257a0da502f3c201c5e871bd1
parentea4d2741a2b574407b0bd387ccd6a8202c014fc5 (diff)
downloadgitlab-ce-06376be56a84b43976b63aad91638fb2c00fec1a.tar.gz
Decouple SAML authentication from the default Omniauth logic
-rw-r--r--CHANGELOG3
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb54
-rw-r--r--config/gitlab.yml.example11
-rw-r--r--lib/gitlab/ldap/user.rb4
-rw-r--r--lib/gitlab/o_auth/user.rb9
-rw-r--r--lib/gitlab/saml/user.rb47
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb6
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb271
8 files changed, 378 insertions, 27 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b0d08d97226..a889ce744ed 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -64,6 +64,9 @@ v 8.5.0 (unreleased)
- Fix broken link to project in build notification emails
- Ability to see and sort on vote count from Issues and MR lists
- Fix builds scheduler when first build in stage was allowed to fail
+ - Allow SAML users to login with no previous account without having to allow
+ all Omniauth providers to do so.
+ - Allow existing users to auto link their SAML credentials by logging in via SAML
v 8.4.4
- Update omniauth-saml gem to 1.4.2
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 9cf76521a0d..21135f7d607 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -42,6 +42,26 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
+ def saml
+ if current_user
+ log_audit_event(current_user, with: :saml)
+ # Update SAML identity if data has changed.
+ identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml)
+ if identity.nil?
+ current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
+ redirect_to profile_account_path, notice: 'Authentication method updated'
+ else
+ redirect_to after_sign_in_path_for(current_user)
+ end
+ else
+ saml_user = Gitlab::Saml::User.new(oauth)
+ saml_user.save
+ @user = saml_user.gl_user
+
+ continue_login_process
+ end
+ end
+
def omniauth_error
@provider = params[:provider]
@error = params[:error]
@@ -65,25 +85,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
- @user = Gitlab::OAuth::User.new(oauth)
- @user.save
+ oauth_user = Gitlab::OAuth::User.new(oauth)
+ oauth_user.save
+ @user = oauth_user.gl_user
- # Only allow properly saved users to login.
- if @user.persisted? && @user.valid?
- log_audit_event(@user.gl_user, with: oauth['provider'])
- sign_in_and_redirect(@user.gl_user)
- else
- error_message =
- if @user.gl_user.errors.any?
- @user.gl_user.errors.map do |attribute, message|
- "#{attribute} #{message}"
- end.join(", ")
- else
- ''
- end
-
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
- end
+ continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
@@ -104,6 +110,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket
end
+ def continue_login_process
+ # Only allow properly saved users to login.
+ if @user.persisted? && @user.valid?
+ log_audit_event(@user, with: oauth['provider'])
+ sign_in_and_redirect(@user)
+ else
+ error_message = @user.errors.full_messages.to_sentence
+
+ redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
+ end
+ end
+
def oauth
@oauth ||= request.env['omniauth.auth']
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index faf05ecd466..b6954b3152b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -288,15 +288,22 @@ production: &base
# auto_sign_in_with_provider: saml
# CAUTION!
- # This allows users to login without having a user account first (default: false).
+ # This allows users to login without having a user account first. Define the allowed
+ # providers using an array, e.g. ["saml", "twitter"]
# User accounts will be created automatically when authentication was successful.
- allow_single_sign_on: false
+ allow_single_sign_on: ["saml"]
+
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
# Look up new users in LDAP servers. If a match is found (same uid), automatically
# link the omniauth identity with the LDAP account. (default: false)
auto_link_ldap_user: false
+ # Allow users with existing accounts to login and auto link their account via SAML
+ # login, without having to do a manual login first and manually add SAML
+ # (default: false)
+ auto_link_saml_user: false
+
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
# If your favorite auth provider is not listed you can use others:
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index e044f0ecc6d..b84c81f1a6c 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -24,6 +24,10 @@ module Gitlab
update_user_attributes
end
+ def save
+ super('LDAP')
+ end
+
# instance methods
def gl_user
@gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index d87a72f7ba3..675ded92a89 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -26,7 +26,7 @@ module Gitlab
gl_user.try(:valid?)
end
- def save
+ def save(provider = 'OAuth')
unauthorized_to_create unless gl_user
if needs_blocking?
@@ -36,10 +36,10 @@ module Gitlab
gl_user.save!
end
- log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
+ log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
- log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
+ log.info "(#{provider}) Error saving user: #{gl_user.errors.full_messages}"
return self, e.record.errors
end
@@ -105,7 +105,8 @@ module Gitlab
end
def signup_enabled?
- Gitlab.config.omniauth.allow_single_sign_on
+ providers = Gitlab.config.omniauth.allow_single_sign_on
+ providers.include?(auth_hash.provider)
end
def block_after_signup?
diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb
new file mode 100644
index 00000000000..b1e30110ef5
--- /dev/null
+++ b/lib/gitlab/saml/user.rb
@@ -0,0 +1,47 @@
+# SAML extension for User model
+#
+# * Find GitLab user based on SAML uid and provider
+# * Create new user from SAML data
+#
+module Gitlab
+ module Saml
+ class User < Gitlab::OAuth::User
+
+ def save
+ super('SAML')
+ end
+
+ def gl_user
+ @user ||= find_by_uid_and_provider
+
+ if auto_link_ldap_user?
+ @user ||= find_or_create_ldap_user
+ end
+
+ if auto_link_saml_enabled?
+ @user ||= find_by_email
+ end
+
+ if signup_enabled?
+ @user ||= build_new_user
+ end
+
+ @user
+ end
+
+ def find_by_email
+ if auth_hash.has_email?
+ user = ::User.find_by(email: auth_hash.email.downcase)
+ user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) if user
+ user
+ end
+ end
+
+ protected
+
+ def auto_link_saml_enabled?
+ Gitlab.config.omniauth.auto_link_saml_user
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 925bc442a90..76b1360f208 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::OAuth::User, lib: true do
describe 'signup' do
shared_examples "to verify compliance with allow_single_sign_on" do
context "with allow_single_sign_on enabled" do
- before { stub_omniauth_config(allow_single_sign_on: true) }
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
it "creates a user from Omniauth" do
oauth_user.save
@@ -55,7 +55,7 @@ describe Gitlab::OAuth::User, lib: true do
end
context "with allow_single_sign_on disabled (Default)" do
- before { stub_omniauth_config(allow_single_sign_on: false) }
+ before { stub_omniauth_config(allow_single_sign_on: []) }
it "throws an error" do
expect{ oauth_user.save }.to raise_error StandardError
end
@@ -135,7 +135,7 @@ describe Gitlab::OAuth::User, lib: true do
describe 'blocking' do
let(:provider) { 'twitter' }
- before { stub_omniauth_config(allow_single_sign_on: true) }
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
context 'signup with omniauth only' do
context 'dont block on create' do
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
new file mode 100644
index 00000000000..480ca1aee4b
--- /dev/null
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -0,0 +1,271 @@
+require 'spec_helper'
+
+describe Gitlab::Saml::User, lib: true do
+ let(:saml_user) { described_class.new(auth_hash) }
+ let(:gl_user) { saml_user.gl_user }
+ let(:uid) { 'my-uid' }
+ let(:provider) { 'saml' }
+ let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) }
+ 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
+ def stub_omniauth_config(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+
+ def stub_ldap_config(messages)
+ allow(Gitlab::LDAP::Config).to receive_messages(messages)
+ end
+
+ describe 'account exists on server' do
+ before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) }
+ context 'and should bind with SAML' do
+ let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
+ 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
+ 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 { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+
+ 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 { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+ it 'should not throw an error' do
+ expect{ saml_user.save }.not_to raise_error
+ end
+ end
+
+ context 'with allow_single_sign_on disabled' do
+ before { stub_omniauth_config(allow_single_sign_on: []) }
+ it 'should throw an error' do
+ expect{ saml_user.save }.to raise_error StandardError
+ end
+ end
+ end
+
+ context 'with auto_link_ldap_user disabled (default)' do
+ before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) }
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'with auto_link_ldap_user enabled' do
+ before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) }
+
+ context 'and no LDAP provider defined' do
+ before { stub_ldap_config(providers: []) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'and at least one LDAP provider is defined' do
+ before { stub_ldap_config(providers: %w(ldapmain)) }
+
+ 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) { 'uid=user1,ou=People,dc=example' }
+ allow(Gitlab::LDAP::Person).to receive(:find_by_uid).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 'johndoe@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ it "adds the omniauth identity to the LDAP account" do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+ end
+
+ context 'and no corresponding LDAP person' do
+ before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+ end
+ end
+
+ end
+
+ describe 'blocking' do
+ before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) }
+
+ context 'signup with SAML only' do
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ it 'should 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 { stub_omniauth_config(block_auto_created_users: true) }
+
+ it 'should block user' do
+ saml_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) { 'uid=user1,ou=People,dc=example' }
+ allow(saml_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_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
+ saml_user.save
+ saml_user.gl_user.activate
+ end
+
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ 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 { stub_omniauth_config(block_auto_created_users: true) }
+
+ it do
+ saml_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 { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+ end
+end