Commit c5d4faf7 authored by Thomas Mendoza's avatar Thomas Mendoza Committed by Imre Farkas

Enable Crowd auth for git-over-https

These changes enable git authentication via Crowd for git requests using
https. This is similar to the existing LDAP authenticator. There are
also changes to the User model to prevent the password warning message
from showing if Crowd is enabled.
parent b586add0
...@@ -18,6 +18,9 @@ class Identity < ApplicationRecord ...@@ -18,6 +18,9 @@ class Identity < ApplicationRecord
scope :with_extern_uid, ->(provider, extern_uid) do scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider) iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
end end
scope :with_any_extern_uid, ->(provider) do
where.not(extern_uid: nil).with_provider(provider)
end
def ldap? def ldap?
Gitlab::Auth::OAuth::Provider.ldap_provider?(provider) Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
......
...@@ -1045,7 +1045,7 @@ class User < ApplicationRecord ...@@ -1045,7 +1045,7 @@ class User < ApplicationRecord
end end
def require_personal_access_token_creation_for_git_auth? def require_personal_access_token_creation_for_git_auth?
return false if allow_password_authentication_for_git? || ldap_user? return false if allow_password_authentication_for_git? || password_based_omniauth_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end end
...@@ -1063,7 +1063,7 @@ class User < ApplicationRecord ...@@ -1063,7 +1063,7 @@ class User < ApplicationRecord
end end
def allow_password_authentication_for_git? def allow_password_authentication_for_git?
Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !ldap_user? Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !password_based_omniauth_user?
end end
def can_change_username? def can_change_username?
...@@ -1143,6 +1143,18 @@ class User < ApplicationRecord ...@@ -1143,6 +1143,18 @@ class User < ApplicationRecord
namespace.find_fork_of(project) namespace.find_fork_of(project)
end end
def password_based_omniauth_user?
ldap_user? || crowd_user?
end
def crowd_user?
if identities.loaded?
identities.find { |identity| identity.provider == 'crowd' && identity.extern_uid.present? }
else
identities.with_any_extern_uid('crowd').exists?
end
end
def ldap_user? def ldap_user?
if identities.loaded? if identities.loaded?
identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? } identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
......
---
title: Enable Crowd auth for git-over-https
merge_request: 46935
author: Thomas Mendoza @tgmachina
type: added
...@@ -7,7 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -7,7 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Atlassian Crowd OmniAuth Provider # Atlassian Crowd OmniAuth Provider
Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
this provider also allows Crowd authentication for Git-over-https requests.
## Configure a new Crowd application ## Configure a new Crowd application
......
# frozen_string_literal: true
module Gitlab
module Auth
module Crowd
class Authentication < Gitlab::Auth::OAuth::Authentication
def login(login, password)
return unless Gitlab::Auth::OAuth::Provider.enabled?(@provider)
return unless login.present? && password.present?
user_info = user_info_from_authentication(login, password)
return unless user_info&.key?(:user)
Gitlab::Auth::OAuth::User.find_by_uid_and_provider(user_info[:user], provider)
end
private
def config
gitlab_crowd_config = Gitlab::Auth::OAuth::Provider.config_for(@provider)
raise "OmniAuth Crowd is not configured." unless gitlab_crowd_config && gitlab_crowd_config[:args]
OmniAuth::Strategies::Crowd::Configuration.new(
gitlab_crowd_config[:args].symbolize_keys)
end
def user_info_from_authentication(login, password)
validator = OmniAuth::Strategies::Crowd::CrowdValidator.new(
config, login, password, RequestContext.instance.client_ip, nil)
validator&.user_info&.symbolize_keys
end
end
end
end
end
...@@ -11,16 +11,6 @@ module Gitlab ...@@ -11,16 +11,6 @@ module Gitlab
module Ldap module Ldap
class User < Gitlab::Auth::OAuth::User class User < Gitlab::Auth::OAuth::User
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
class << self
# rubocop: disable CodeReuse/ActiveRecord
def find_by_uid_and_provider(uid, provider)
identity = ::Identity.with_extern_uid(provider, uid).take
identity && identity.user
end
# rubocop: enable CodeReuse/ActiveRecord
end
def save def save
super('LDAP') super('LDAP')
end end
...@@ -30,10 +20,6 @@ module Gitlab ...@@ -30,10 +20,6 @@ module Gitlab
find_by_uid_and_provider || find_by_email || build_new_user find_by_uid_and_provider || find_by_email || build_new_user
end end
def find_by_uid_and_provider
self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
end
override :should_save? override :should_save?
def should_save? def should_save?
gl_user.changed? || gl_user.identities.any?(&:changed?) gl_user.changed? || gl_user.identities.any?(&:changed?)
......
...@@ -18,6 +18,8 @@ module Gitlab ...@@ -18,6 +18,8 @@ module Gitlab
authenticator = authenticator =
case provider case provider
when /crowd/
Gitlab::Auth::Crowd::Authentication
when /^ldap/ when /^ldap/
Gitlab::Auth::Ldap::Authentication Gitlab::Auth::Ldap::Authentication
when 'database' when 'database'
......
...@@ -9,6 +9,16 @@ module Gitlab ...@@ -9,6 +9,16 @@ module Gitlab
module Auth module Auth
module OAuth module OAuth
class User class User
class << self
# rubocop: disable CodeReuse/ActiveRecord
def find_by_uid_and_provider(uid, provider)
identity = ::Identity.with_extern_uid(provider, uid).take
identity && identity.user
end
# rubocop: enable CodeReuse/ActiveRecord
end
SignupDisabledError = Class.new(StandardError) SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError) SigninDisabledForProviderError = Class.new(StandardError)
...@@ -190,12 +200,9 @@ module Gitlab ...@@ -190,12 +200,9 @@ module Gitlab
@auth_hash = AuthHash.new(auth_hash) @auth_hash = AuthHash.new(auth_hash)
end end
# rubocop: disable CodeReuse/ActiveRecord
def find_by_uid_and_provider def find_by_uid_and_provider
identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
identity&.user
end end
# rubocop: enable CodeReuse/ActiveRecord
def build_new_user def build_new_user
user_params = user_attributes.merge(skip_confirmation: true) user_params = user_attributes.merge(skip_confirmation: true)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Auth::Crowd::Authentication do
let(:provider) { 'crowd' }
let(:login) { generate(:username) }
let(:password) { 'password' }
let(:crowd_auth) { described_class.new(provider) }
let(:user_info) { { user: login } }
describe 'login' do
before do
allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with(provider).and_return(true)
allow(crowd_auth).to receive(:user_info_from_authentication).and_return(user_info)
end
it "finds the user if authentication is successful" do
create(:omniauth_user, extern_uid: login, username: login, provider: provider)
expect(crowd_auth.login(login, password)).to be_truthy
end
it "is false if the user does not exist" do
expect(crowd_auth.login(login, password)).to be_falsey
end
it "is false if the authentication fails" do
allow(crowd_auth).to receive(:user_info_from_authentication).and_return(nil)
expect(crowd_auth.login(login, password)).to be_falsey
end
it "fails when crowd is disabled" do
allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with('crowd').and_return(false)
expect(crowd_auth.login(login, password)).to be_falsey
end
it "fails if no login is supplied" do
expect(crowd_auth.login('', password)).to be_falsey
end
it "fails if no password is supplied" do
expect(crowd_auth.login(login, '')).to be_falsey
end
end
end
...@@ -49,23 +49,6 @@ RSpec.describe Gitlab::Auth::Ldap::User do ...@@ -49,23 +49,6 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end end
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 describe 'find or create' do
it "finds the user if already existing" 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') create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
......
...@@ -25,6 +25,23 @@ RSpec.describe Gitlab::Auth::OAuth::User do ...@@ -25,6 +25,23 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') } let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
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 '#persisted?' do describe '#persisted?' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
......
...@@ -81,6 +81,36 @@ RSpec.describe Identity do ...@@ -81,6 +81,36 @@ RSpec.describe Identity do
end end
end end
describe '.with_any_extern_uid' do
context 'provider with extern uid' do
let!(:test_entity) { create(:identity, provider: 'test_provider', extern_uid: 'test_uid') }
it 'finds any extern uids associated with a provider' do
identity = described_class.with_any_extern_uid('test_provider').first
expect(identity).to be
end
end
context 'provider with nil extern uid' do
let!(:nil_entity) { create(:identity, provider: 'nil_entity_provider', extern_uid: nil) }
it 'has no results when there are no extern uids' do
identity = described_class.with_any_extern_uid('nil_entity_provider').first
expect(identity).to be_nil
end
end
context 'no provider' do
it 'has no results when there is no associated provider' do
identity = described_class.with_any_extern_uid('nonexistent_provider').first
expect(identity).to be_nil
end
end
end
context 'callbacks' do context 'callbacks' do
context 'before_save' do context 'before_save' do
describe 'normalizes extern uid' do describe 'normalizes extern uid' do
......
...@@ -2535,6 +2535,28 @@ RSpec.describe User do ...@@ -2535,6 +2535,28 @@ RSpec.describe User do
end end
end end
context 'crowd synchronized user' do
describe '#crowd_user?' do
it 'is true if provider is crowd' do
user = create(:omniauth_user, provider: 'crowd')
expect(user.crowd_user?).to be_truthy
end
it 'is false for other providers' do
user = create(:omniauth_user, provider: 'other-provider')
expect(user.crowd_user?).to be_falsey
end
it 'is false if no extern_uid is provided' do
user = create(:omniauth_user, extern_uid: nil)
expect(user.crowd_user?).to be_falsey
end
end
end
describe '#requires_ldap_check?' do describe '#requires_ldap_check?' do
let(:user) { described_class.new } let(:user) { described_class.new }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment