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
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
end
scope :with_any_extern_uid, ->(provider) do
where.not(extern_uid: nil).with_provider(provider)
end
def ldap?
Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
......
......@@ -1045,7 +1045,7 @@ class User < ApplicationRecord
end
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?
end
......@@ -1063,7 +1063,7 @@ class User < ApplicationRecord
end
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
def can_change_username?
......@@ -1143,6 +1143,18 @@ class User < ApplicationRecord
namespace.find_fork_of(project)
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?
if identities.loaded?
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
# 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
......
# 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
module Ldap
class User < Gitlab::Auth::OAuth::User
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
super('LDAP')
end
......@@ -30,10 +20,6 @@ module Gitlab
find_by_uid_and_provider || find_by_email || build_new_user
end
def find_by_uid_and_provider
self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
end
override :should_save?
def should_save?
gl_user.changed? || gl_user.identities.any?(&:changed?)
......
......@@ -18,6 +18,8 @@ module Gitlab
authenticator =
case provider
when /crowd/
Gitlab::Auth::Crowd::Authentication
when /^ldap/
Gitlab::Auth::Ldap::Authentication
when 'database'
......
......@@ -9,6 +9,16 @@ module Gitlab
module Auth
module OAuth
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)
SigninDisabledForProviderError = Class.new(StandardError)
......@@ -190,12 +200,9 @@ module Gitlab
@auth_hash = AuthHash.new(auth_hash)
end
# rubocop: disable CodeReuse/ActiveRecord
def find_by_uid_and_provider
identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity&.user
self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
end
# rubocop: enable CodeReuse/ActiveRecord
def build_new_user
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
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')
......
......@@ -25,6 +25,23 @@ RSpec.describe Gitlab::Auth::OAuth::User do
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
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
......
......@@ -81,6 +81,36 @@ RSpec.describe Identity do
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 'before_save' do
describe 'normalizes extern uid' do
......
......@@ -2535,6 +2535,28 @@ RSpec.describe User do
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
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