Commit 11d275e2 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/move_oauth_modules_to_auth_dir_structure' into 'master'

Moved o_auth/saml/ldap modules under gitlab/auth

See merge request gitlab-org/gitlab-ce!17359
parents 008120f8 1ad5df49
...@@ -124,8 +124,8 @@ Lint/DuplicateMethods: ...@@ -124,8 +124,8 @@ Lint/DuplicateMethods:
- 'lib/gitlab/git/repository.rb' - 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/tree.rb' - 'lib/gitlab/git/tree.rb'
- 'lib/gitlab/git/wiki_page.rb' - 'lib/gitlab/git/wiki_page.rb'
- 'lib/gitlab/ldap/person.rb' - 'lib/gitlab/auth/ldap/person.rb'
- 'lib/gitlab/o_auth/user.rb' - 'lib/gitlab/auth/o_auth/user.rb'
# Offense count: 4 # Offense count: 4
Lint/InterpolationCheck: Lint/InterpolationCheck:
...@@ -812,7 +812,7 @@ Style/TrivialAccessors: ...@@ -812,7 +812,7 @@ Style/TrivialAccessors:
Exclude: Exclude:
- 'app/models/external_issue.rb' - 'app/models/external_issue.rb'
- 'app/serializers/base_serializer.rb' - 'app/serializers/base_serializer.rb'
- 'lib/gitlab/ldap/person.rb' - 'lib/gitlab/auth/ldap/person.rb'
- 'lib/system_check/base_check.rb' - 'lib/system_check/base_check.rb'
# Offense count: 4 # Offense count: 4
......
...@@ -191,7 +191,7 @@ class ApplicationController < ActionController::Base ...@@ -191,7 +191,7 @@ class ApplicationController < ActionController::Base
return unless signed_in? && session[:service_tickets] return unless signed_in? && session[:service_tickets]
valid = session[:service_tickets].all? do |provider, ticket| valid = session[:service_tickets].all? do |provider, ticket|
Gitlab::OAuth::Session.valid?(provider, ticket) Gitlab::Auth::OAuth::Session.valid?(provider, ticket)
end end
unless valid unless valid
...@@ -215,7 +215,7 @@ class ApplicationController < ActionController::Base ...@@ -215,7 +215,7 @@ class ApplicationController < ActionController::Base
if current_user && current_user.requires_ldap_check? if current_user && current_user.requires_ldap_check?
return unless current_user.try_obtain_ldap_lease return unless current_user.try_obtain_ldap_lease
unless Gitlab::LDAP::Access.allowed?(current_user) unless Gitlab::Auth::LDAP::Access.allowed?(current_user)
sign_out current_user sign_out current_user
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
...@@ -230,7 +230,7 @@ class ApplicationController < ActionController::Base ...@@ -230,7 +230,7 @@ class ApplicationController < ActionController::Base
end end
def gitlab_ldap_access(&block) def gitlab_ldap_access(&block)
Gitlab::LDAP::Access.open { |access| yield(access) } Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end end
# JSON for infinite scroll via Pager object # JSON for infinite scroll via Pager object
...@@ -284,7 +284,7 @@ class ApplicationController < ActionController::Base ...@@ -284,7 +284,7 @@ class ApplicationController < ActionController::Base
end end
def github_import_configured? def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github) Gitlab::Auth::OAuth::Provider.enabled?(:github)
end end
def gitlab_import_enabled? def gitlab_import_enabled?
...@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base ...@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base
end end
def gitlab_import_configured? def gitlab_import_configured?
Gitlab::OAuth::Provider.enabled?(:gitlab) Gitlab::Auth::OAuth::Provider.enabled?(:gitlab)
end end
def bitbucket_import_enabled? def bitbucket_import_enabled?
...@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base ...@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base
end end
def bitbucket_import_configured? def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket) Gitlab::Auth::OAuth::Provider.enabled?(:bitbucket)
end end
def google_code_import_enabled? def google_code_import_enabled?
......
...@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController
end end
def provider def provider
Gitlab::OAuth::Provider.config_for('bitbucket') Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end end
def options def options
......
...@@ -10,8 +10,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -10,8 +10,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
end end
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::LDAP::Config.available_servers.each do |server| Gitlab::Auth::LDAP::Config.available_servers.each do |server|
define_method server['provider_name'] do define_method server['provider_name'] do
ldap ldap
end end
...@@ -31,7 +31,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -31,7 +31,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# We only find ourselves here # We only find ourselves here
# if the authentication to LDAP was successful. # if the authentication to LDAP was successful.
def ldap def ldap
ldap_user = Gitlab::LDAP::User.new(oauth) ldap_user = Gitlab::Auth::LDAP::User.new(oauth)
ldap_user.save if ldap_user.changed? # will also save new users ldap_user.save if ldap_user.changed? # will also save new users
@user = ldap_user.gl_user @user = ldap_user.gl_user
...@@ -62,13 +62,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -62,13 +62,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to after_sign_in_path_for(current_user) redirect_to after_sign_in_path_for(current_user)
end end
else else
saml_user = Gitlab::Saml::User.new(oauth) saml_user = Gitlab::Auth::Saml::User.new(oauth)
saml_user.save if saml_user.changed? saml_user.save if saml_user.changed?
@user = saml_user.gl_user @user = saml_user.gl_user
continue_login_process continue_login_process
end end
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error handle_signup_error
end end
...@@ -106,20 +106,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -106,20 +106,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider']) log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated' redirect_to profile_account_path, notice: 'Authentication method updated'
else else
oauth_user = Gitlab::OAuth::User.new(oauth) oauth_user = Gitlab::Auth::OAuth::User.new(oauth)
oauth_user.save oauth_user.save
@user = oauth_user.gl_user @user = oauth_user.gl_user
continue_login_process continue_login_process
end end
rescue Gitlab::OAuth::SigninDisabledForProviderError rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider handle_disabled_provider
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error handle_signup_error
end end
def handle_service_ticket(provider, ticket) def handle_service_ticket(provider, ticket)
Gitlab::OAuth::Session.create provider, ticket Gitlab::Auth::OAuth::Session.create provider, ticket
session[:service_tickets] ||= {} session[:service_tickets] ||= {}
session[:service_tickets][provider] = ticket session[:service_tickets][provider] = ticket
end end
...@@ -142,7 +142,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -142,7 +142,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
def handle_signup_error def handle_signup_error
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if Gitlab::CurrentSettings.allow_signup? if Gitlab::CurrentSettings.allow_signup?
...@@ -171,7 +171,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -171,7 +171,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
def handle_disabled_provider def handle_disabled_provider
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
flash[:alert] = "Signing in using #{label} has been disabled" flash[:alert] = "Signing in using #{label} has been disabled"
redirect_to new_user_session_path redirect_to new_user_session_path
......
...@@ -16,7 +16,7 @@ class SessionsController < Devise::SessionsController ...@@ -16,7 +16,7 @@ class SessionsController < Devise::SessionsController
def new def new
set_minimum_password_length set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers @ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super super
end end
......
...@@ -77,7 +77,7 @@ module ApplicationSettingsHelper ...@@ -77,7 +77,7 @@ module ApplicationSettingsHelper
label_tag(checkbox_name, class: css_class) do label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled, check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source) autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
end end
end end
end end
......
...@@ -3,7 +3,7 @@ module AuthHelper ...@@ -3,7 +3,7 @@ module AuthHelper
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled? def ldap_enabled?
Gitlab::LDAP::Config.enabled? Gitlab::Auth::LDAP::Config.enabled?
end end
def omniauth_enabled? def omniauth_enabled?
...@@ -15,11 +15,11 @@ module AuthHelper ...@@ -15,11 +15,11 @@ module AuthHelper
end end
def auth_providers def auth_providers
Gitlab::OAuth::Provider.providers Gitlab::Auth::OAuth::Provider.providers
end end
def label_for_provider(name) def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name) Gitlab::Auth::OAuth::Provider.label_for(name)
end end
def form_based_provider?(name) def form_based_provider?(name)
......
...@@ -3,7 +3,7 @@ module ProfilesHelper ...@@ -3,7 +3,7 @@ module ProfilesHelper
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute) if user_synced_attributes_metadata&.synced?(attribute)
if user_synced_attributes_metadata.provider if user_synced_attributes_metadata.provider
Gitlab::OAuth::Provider.label_for(user_synced_attributes_metadata.provider) Gitlab::Auth::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
else else
'LDAP' 'LDAP'
end end
......
...@@ -17,12 +17,12 @@ class Identity < ActiveRecord::Base ...@@ -17,12 +17,12 @@ class Identity < ActiveRecord::Base
end end
def ldap? def ldap?
Gitlab::OAuth::Provider.ldap_provider?(provider) Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
end end
def self.normalize_uid(provider, uid) def self.normalize_uid(provider, uid)
if Gitlab::OAuth::Provider.ldap_provider?(provider) if Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
Gitlab::LDAP::Person.normalize_dn(uid) Gitlab::Auth::LDAP::Person.normalize_dn(uid)
else else
uid.to_s uid.to_s
end end
......
...@@ -728,7 +728,7 @@ class User < ActiveRecord::Base ...@@ -728,7 +728,7 @@ class User < ActiveRecord::Base
def ldap_user? def ldap_user?
if identities.loaded? if identities.loaded?
identities.find { |identity| Gitlab::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? }
else else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end end
......
...@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base ...@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
private private
def sync_profile_from_provider? def sync_profile_from_provider?
Gitlab::OAuth::Provider.sync_profile_from_provider?(provider) Gitlab::Auth::OAuth::Provider.sync_profile_from_provider?(provider)
end end
end end
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
Password authentication enabled for Git over HTTP(S) Password authentication enabled for Git over HTTP(S)
.help-block .help-block
When disabled, a Personal Access Token When disabled, a Personal Access Token
- if Gitlab::LDAP::Config.enabled? - if Gitlab::Auth::LDAP::Config.enabled?
or LDAP password or LDAP password
must be used to authenticate. must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.form-group .form-group
= f.label :provider, class: 'control-label' = f.label :provider, class: 'control-label'
.col-sm-10 .col-sm-10
- values = Gitlab::OAuth::Provider.providers.map { |name| ["#{Gitlab::OAuth::Provider.label_for(name)} (#{name})", name] } - values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control' = f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group .form-group
= f.label :extern_uid, "Identifier", class: 'control-label' = f.label :extern_uid, "Identifier", class: 'control-label'
......
%tr %tr
%td %td
#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider}) #{Gitlab::Auth::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td %td
= identity.extern_uid = identity.extern_uid
%td %td
......
---
title: Moved o_auth/saml/ldap modules under gitlab/auth
merge_request: 17359
author: Horatiu Eugen Vlad
...@@ -212,9 +212,9 @@ Devise.setup do |config| ...@@ -212,9 +212,9 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy # manager.default_strategies(scope: :user).unshift :some_external_strategy
# end # end
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::LDAP::Config.providers.each do |provider| Gitlab::Auth::LDAP::Config.providers.each do |provider|
ldap_config = Gitlab::LDAP::Config.new(provider) ldap_config = Gitlab::Auth::LDAP::Config.new(provider)
config.omniauth(provider, ldap_config.omniauth_options) config.omniauth(provider, ldap_config.omniauth_options)
end end
end end
...@@ -235,9 +235,9 @@ Devise.setup do |config| ...@@ -235,9 +235,9 @@ Devise.setup do |config|
if provider['name'] == 'cas3' if provider['name'] == 'cas3'
provider['args'][:on_single_sign_out] = lambda do |request| provider['args'][:on_single_sign_out] = lambda do |request|
ticket = request.params[:session_index] ticket = request.params[:session_index]
raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket) raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
Gitlab::OAuth::Session.destroy(:cas3, ticket) Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
true true
end end
end end
...@@ -245,8 +245,8 @@ Devise.setup do |config| ...@@ -245,8 +245,8 @@ Devise.setup do |config|
if provider['name'] == 'authentiq' if provider['name'] == 'authentiq'
provider['args'][:remote_sign_out_handler] = lambda do |request| provider['args'][:remote_sign_out_handler] = lambda do |request|
authentiq_session = request.params['sid'] authentiq_session = request.params['sid']
if Gitlab::OAuth::Session.valid?(:authentiq, authentiq_session) if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
Gitlab::OAuth::Session.destroy(:authentiq, authentiq_session) Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
true true
else else
false false
......
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
module OmniAuth::Strategies module OmniAuth::Strategies
Gitlab::LDAP::Config.available_servers.each do |server| Gitlab::Auth::LDAP::Config.available_servers.each do |server|
# do not redeclare LDAP # do not redeclare LDAP
next if server['provider_name'] == 'ldap' next if server['provider_name'] == 'ldap'
......
...@@ -57,7 +57,7 @@ module Bitbucket ...@@ -57,7 +57,7 @@ module Bitbucket
end end
def provider def provider
Gitlab::OAuth::Provider.config_for('bitbucket') Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end end
def options def options
......
...@@ -54,7 +54,7 @@ module Gitlab ...@@ -54,7 +54,7 @@ module Gitlab
# LDAP users are only authenticated via LDAP # LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user? if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication # Second chance - try LDAP authentication
Gitlab::LDAP::Authentication.login(login, password) Gitlab::Auth::LDAP::Authentication.login(login, password)
elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git? elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git?
user if user.active? && user.valid_password?(password) user if user.active? && user.valid_password?(password)
end end
...@@ -85,7 +85,7 @@ module Gitlab ...@@ -85,7 +85,7 @@ module Gitlab
private private
def authenticate_using_internal_or_ldap_password? def authenticate_using_internal_or_ldap_password?
Gitlab::CurrentSettings.password_authentication_enabled_for_git? || Gitlab::LDAP::Config.enabled? Gitlab::CurrentSettings.password_authentication_enabled_for_git? || Gitlab::Auth::LDAP::Config.enabled?
end end
def service_request_check(login, password, project) def service_request_check(login, password, project)
......
# LDAP authorization model
#
# * Check if we are allowed access (not blocked)
#
module Gitlab
module Auth
module LDAP
class Access
attr_reader :provider, :user
def self.open(user, &block)
Gitlab::Auth::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
self.open(user) do |access|
if access.allowed?
Users::UpdateService.new(user, user: user, last_credential_check_at: Time.now).execute
true
else
false
end
end
end
def initialize(user, adapter = nil)
@adapter = adapter
@user = user
@provider = user.ldap_identity.provider
end
def allowed?
if ldap_user
unless ldap_config.active_directory
unblock_user(user, 'is available again') if user.ldap_blocked?
return true
end
# Block user in GitLab if he/she was blocked in AD
if Gitlab::Auth::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
block_user(user, 'is disabled in Active Directory')
false
else
unblock_user(user, 'is not disabled anymore') if user.ldap_blocked?
true
end
else
# Block the user if they no longer exist in LDAP/AD
block_user(user, 'does not exist anymore')
false
end
end
def adapter
@adapter ||= Gitlab::Auth::LDAP::Adapter.new(provider)
end
def ldap_config
Gitlab::Auth::LDAP::Config.new(provider)
end
def ldap_user
@ldap_user ||= Gitlab::Auth::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
end
def block_user(user, reason)
user.ldap_block
Gitlab::AppLogger.info(
"LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \
"blocking Gitlab user \"#{user.name}\" (#{user.email})"
)
end
def unblock_user(user, reason)
user.activate
Gitlab::AppLogger.info(
"LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \
"unblocking Gitlab user \"#{user.name}\" (#{user.email})"
)
end
end
end
end
end
module Gitlab
module Auth
module LDAP
class Adapter
attr_reader :provider, :ldap
def self.open(provider, &block)
Net::LDAP.open(config(provider).adapter_options) do |ldap|
block.call(self.new(provider, ldap))
end
end
def self.config(provider)
Gitlab::Auth::LDAP::Config.new(provider)
end
def initialize(provider, ldap = nil)
@provider = provider
@ldap = ldap || Net::LDAP.new(config.adapter_options)
end
def config
Gitlab::Auth::LDAP::Config.new(provider)
end
def users(fields, value, limit = nil)
options = user_options(Array(fields), value, limit)
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
Gitlab::Auth::LDAP::Person.new(entry, provider)
end
end
def user(*args)
users(*args).first
end
def dn_matches_filter?(dn, filter)
ldap_search(base: dn,
filter: filter,
scope: Net::LDAP::SearchScope_BaseObject,
attributes: %w{dn}).any?
end
def ldap_search(*args)
# Net::LDAP's `time` argument doesn't work. Use Ruby `Timeout` instead.
Timeout.timeout(config.timeout) do
results = ldap.search(*args)
if results.nil?
response = ldap.get_operation_result
unless response.code.zero?
Rails.logger.warn("LDAP search error: #{response.message}")
end
[]
else
results
end
end
rescue Net::LDAP::Error => error
Rails.logger.warn("LDAP search raised exception #{error.class}: #{error.message}")
[]
rescue Timeout::Error
Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
[]
end
private
def user_options(fields, value, limit)
options = {
attributes: Gitlab::Auth::LDAP::Person.ldap_attributes(config),
base: config.base
}
options[:size] = limit if limit
if fields.include?('dn')
raise ArgumentError, 'It is not currently possible to search the DN and other fields at the same time.' if fields.size > 1
options[:base] = value
options[:scope] = Net::LDAP::SearchScope_BaseObject
else
filter = fields.map { |field| Net::LDAP::Filter.eq(field, value) }.inject(:|)
end
options.merge(filter: user_filter(filter))
end
def user_filter(filter = nil)
user_filter = config.constructed_user_filter if config.user_filter.present?
if user_filter && filter
Net::LDAP::Filter.join(filter, user_filter)
elsif user_filter
user_filter
else
filter
end
end
end
end
end
end
# Class to parse and transform the info provided by omniauth
#
module Gitlab
module Auth
module LDAP
class AuthHash < Gitlab::Auth::OAuth::AuthHash
def uid
@uid ||= Gitlab::Auth::LDAP::Person.normalize_dn(super)
end
def username
super.tap do |username|
username.downcase! if ldap_config.lowercase_usernames
end
end
private
def get_info(key)
attributes = ldap_config.attributes[key.to_s]
return super unless attributes
attributes = Array(attributes)
value = nil
attributes.each do |attribute|
value = get_raw(attribute)
value = value.first if value
break if value.present?
end
return super unless value
Gitlab::Utils.force_utf8(value)
value
end
def get_raw(key)
auth_hash.extra[:raw_info][key] if auth_hash.extra
end
def ldap_config
@ldap_config ||= Gitlab::Auth::LDAP::Config.new(self.provider)
end
end
end
end
end
# These calls help to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
# until a valid bind is found
#
module Gitlab
module Auth
module LDAP
class Authentication
def self.login(login, password)
return unless Gitlab::Auth::LDAP::Config.enabled?
return unless login.present? && password.present?
auth = nil
# loop through providers until valid bind
providers.find do |provider|
auth = new(provider)
auth.login(login, password) # true will exit the loop
end
# If (login, password) was invalid for all providers, the value of auth is now the last
# Gitlab::Auth::LDAP::Authentication instance we tried.
auth.user
end
def self.providers
Gitlab::Auth::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
def login(login, password)
@ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
end
def adapter
OmniAuth::LDAP::Adaptor.new(config.omniauth_options)
end
def config
Gitlab::Auth::LDAP::Config.new(provider)
end
def user_filter(login)
filter = Net::LDAP::Filter.equals(config.uid, login)
# Apply LDAP user filter if present
if config.user_filter.present?
filter = Net::LDAP::Filter.join(filter, config.constructed_user_filter)
end
filter
end
def user
return nil unless ldap_user
Gitlab::Auth::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
end
end
end
# Load a specific server configuration
module Gitlab
module Auth
module LDAP
class Config
NET_LDAP_ENCRYPTION_METHOD = {
simple_tls: :simple_tls,
start_tls: :start_tls,
plain: nil
}.freeze
attr_accessor :provider, :options
def self.enabled?
Gitlab.config.ldap.enabled
end
def self.servers
Gitlab.config.ldap['servers']&.values || []
end
def self.available_servers
return [] unless enabled?
Array.wrap(servers.first)
end
def self.providers
servers.map { |server| server['provider_name'] }
end
def self.valid_provider?(provider)
providers.include?(provider)
end
def self.invalid_provider(provider)
raise "Unknown provider (#{provider}). Available providers: #{providers}"
end
def initialize(provider)
if self.class.valid_provider?(provider)
@provider = provider
else
self.class.invalid_provider(provider)
end
@options = config_for(@provider) # Use @provider, not provider
end
def enabled?
base_config.enabled
end
def adapter_options
opts = base_options.merge(
encryption: encryption_options
)
opts.merge!(auth_options) if has_auth?
opts
end
def omniauth_options
opts = base_options.merge(
base: base,
encryption: options['encryption'],
filter: omniauth_user_filter,
name_proc: name_proc,
disable_verify_certificates: !options['verify_certificates']
)
if has_auth?
opts.merge!(
bind_dn: options['bind_dn'],
password: options['password']
)
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts
end
def base
options['base']
end
def uid
options['uid']
end
def sync_ssh_keys?
sync_ssh_keys.present?
end
# The LDAP attribute in which the ssh keys are stored
def sync_ssh_keys
options['sync_ssh_keys']
end
def user_filter
options['user_filter']
end
def constructed_user_filter
@constructed_user_filter ||= Net::LDAP::Filter.construct(user_filter)
end
def group_base
options['group_base']
end
def admin_group
options['admin_group']
end
def active_directory
options['active_directory']
end
def block_auto_created_users
options['block_auto_created_users']
end
def attributes
default_attributes.merge(options['attributes'])
end
def timeout
options['timeout'].to_i
end
def has_auth?
options['password'] || options['bind_dn']
end
def allow_username_or_email_login
options['allow_username_or_email_login']
end
def lowercase_usernames
options['lowercase_usernames']
end
def name_proc
if allow_username_or_email_login
proc { |name| name.gsub(/@.*\z/, '') }
else
proc { |name| name }
end
end
def default_attributes
{
'username' => %w(uid sAMAccountName userid),
'email' => %w(mail email userPrincipalName),
'name' => 'cn',
'first_name' => 'givenName',
'last_name' => 'sn'
}
end
protected
def base_options
{
host: options['host'],
port: options['port']
}
end
def base_config
Gitlab.config.ldap
end
def config_for(provider)
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
def encryption_options
method = translate_method(options['encryption'])
return nil unless method
{
method: method,
tls_options: tls_options(method)
}
end
def translate_method(method_from_config)
NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
end
def tls_options(method)
return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
opts = if options['verify_certificates']
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
else
# It is important to explicitly set verify_mode for two reasons:
# 1. The behavior of OpenSSL is undefined when verify_mode is not set.
# 2. The net-ldap gem implementation verifies the certificate hostname
# unless verify_mode is set to VERIFY_NONE.
{ verify_mode: OpenSSL::SSL::VERIFY_NONE }
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts
end
def auth_options
{
auth: {
method: :simple,
username: options['bind_dn'],
password: options['password']
}
}
end
def omniauth_user_filter
uid_filter = Net::LDAP::Filter.eq(uid, '%{username}')
if user_filter.present?
Net::LDAP::Filter.join(uid_filter, constructed_user_filter).to_s
else
uid_filter.to_s
end
end
end
end
end
end
# -*- ruby encoding: utf-8 -*-
# Based on the `ruby-net-ldap` gem's `Net::LDAP::DN`
#
# For our purposes, this class is used to normalize DNs in order to allow proper
# comparison.
#
# E.g. DNs should be compared case-insensitively (in basically all LDAP
# implementations or setups), therefore we downcase every DN.
##
# Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
# ("Distinguished Name") is a unique identifier for an entry within an LDAP
# directory. It is made up of a number of other attributes strung together,
# to identify the entry in the tree.
#
# Each attribute that makes up a DN needs to have its value escaped so that
# the DN is valid. This class helps take care of that.
#
# A fully escaped DN needs to be unescaped when analysing its contents. This
# class also helps take care of that.
module Gitlab
module Auth
module LDAP
class DN
FormatError = Class.new(StandardError)
MalformedError = Class.new(FormatError)
UnsupportedError = Class.new(FormatError)
def self.normalize_value(given_value)
dummy_dn = "placeholder=#{given_value}"
normalized_dn = new(*dummy_dn).to_normalized_s
normalized_dn.sub(/\Aplaceholder=/, '')
end
##
# Initialize a DN, escaping as required. Pass in attributes in name/value
# pairs. If there is a left over argument, it will be appended to the dn
# without escaping (useful for a base string).
#
# Most uses of this class will be to escape a DN, rather than to parse it,
# so storing the dn as an escaped String and parsing parts as required
# with a state machine seems sensible.
def initialize(*args)
if args.length > 1
initialize_array(args)
else
initialize_string(args[0])
end
end
##
# Parse a DN into key value pairs using ASN from
# http://tools.ietf.org/html/rfc2253 section 3.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def each_pair
state = :key
key = StringIO.new
value = StringIO.new
hex_buffer = ""
@dn.each_char.with_index do |char, dn_index|
case state
when :key then
case char
when 'a'..'z', 'A'..'Z' then
state = :key_normal
key << char
when '0'..'9' then
state = :key_oid
key << char
when ' ' then state = :key
else raise(MalformedError, "Unrecognized first character of an RDN attribute type name \"#{char}\"")
end
when :key_normal then
case char
when '=' then state = :value
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
else raise(MalformedError, "Unrecognized RDN attribute type name character \"#{char}\"")
end
when :key_oid then
case char
when '=' then state = :value
when '0'..'9', '.', ' ' then key << char
else raise(MalformedError, "Unrecognized RDN OID attribute type name character \"#{char}\"")
end
when :value then
case char
when '\\' then state = :value_normal_escape
when '"' then state = :value_quoted
when ' ' then state = :value
when '#' then
state = :value_hexstring
value << char
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else
state = :value_normal
value << char
end
when :value_normal then
case char
when '\\' then state = :value_normal_escape
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
when '+' then raise(UnsupportedError, "Multivalued RDNs are not supported")
else value << char
end
when :value_normal_escape then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal_escape_hex
hex_buffer = char
else
state = :value_normal
value << char
end
when :value_normal_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise(MalformedError, "Invalid escaped hex code \"\\#{hex_buffer}#{char}\"")
end
when :value_quoted then
case char
when '\\' then state = :value_quoted_escape
when '"' then state = :value_end
else value << char
end
when :value_quoted_escape then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted_escape_hex
hex_buffer = char
else
state = :value_quoted
value << char
end
when :value_quoted_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise(MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"#{char}\"")
end
when :value_hexstring then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring_hex
value << char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else raise(MalformedError, "Expected the first character of a hex pair, but got \"#{char}\"")
end
when :value_hexstring_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring
value << char
else raise(MalformedError, "Expected the second character of a hex pair, but got \"#{char}\"")
end
when :value_end then
case char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else raise(MalformedError, "Expected the end of an attribute value, but got \"#{char}\"")
end
else raise "Fell out of state machine"
end
end
# Last pair
raise(MalformedError, 'DN string ended unexpectedly') unless
[:value, :value_normal, :value_hexstring, :value_end].include? state
yield key.string.strip, rstrip_except_escaped(value.string, @dn.length)
end
def rstrip_except_escaped(str, dn_index)
str_ends_with_whitespace = str.match(/\s\z/)
if str_ends_with_whitespace
dn_part_ends_with_escaped_whitespace = @dn[0, dn_index].match(/\\(\s+)\z/)
if dn_part_ends_with_escaped_whitespace
dn_part_rwhitespace = dn_part_ends_with_escaped_whitespace[1]
num_chars_to_remove = dn_part_rwhitespace.length - 1
str = str[0, str.length - num_chars_to_remove]
else
str.rstrip!
end
end
str
end
##
# Returns the DN as an array in the form expected by the constructor.
def to_a
a = []
self.each_pair { |key, value| a << key << value } unless @dn.empty?
a
end
##
# Return the DN as an escaped string.
def to_s
@dn
end
##
# Return the DN as an escaped and normalized string.
def to_normalized_s
self.class.new(*to_a).to_s.downcase
end
# https://tools.ietf.org/html/rfc4514 section 2.4 lists these exceptions
# for DN values. All of the following must be escaped in any normal string
# using a single backslash ('\') as escape. The space character is left
# out here because in a "normalized" string, spaces should only be escaped
# if necessary (i.e. leading or trailing space).
NORMAL_ESCAPES = [',', '+', '"', '\\', '<', '>', ';', '='].freeze
# The following must be represented as escaped hex
HEX_ESCAPES = {
"\n" => '\0a',
"\r" => '\0d'
}.freeze
# Compiled character class regexp using the keys from the above hash, and
# checking for a space or # at the start, or space at the end, of the
# string.
ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
NORMAL_ESCAPES.map { |e| Regexp.escape(e) }.join +
"])")
HEX_ESCAPE_RE = Regexp.new("([" +
HEX_ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
"])")
##
# Escape a string for use in a DN value
def self.escape(string)
escaped = string.gsub(ESCAPE_RE) { |char| "\\" + char }
escaped.gsub(HEX_ESCAPE_RE) { |char| HEX_ESCAPES[char] }
end
private
def initialize_array(args)
buffer = StringIO.new
args.each_with_index do |arg, index|
if index.even? # key
buffer << "," if index > 0
buffer << arg
else # value
buffer << "="
buffer << self.class.escape(arg)
end
end
@dn = buffer.string
end
def initialize_string(arg)
@dn = arg.to_s
end
##
# Proxy all other requests to the string object, because a DN is mainly
# used within the library as a string
# rubocop:disable GitlabSecurity/PublicSend
def method_missing(method, *args, &block)
@dn.send(method, *args, &block)
end
##
# Redefined to be consistent with redefined `method_missing` behavior
def respond_to?(sym, include_private = false)
@dn.respond_to?(sym, include_private)
end
end
end
end
end
module Gitlab
module Auth
module LDAP
class Person
# Active Directory-specific LDAP filter that checks if bit 2 of the
# userAccountControl attribute is set.
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
InvalidEntryError = Class.new(StandardError)
attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter)
uid = Net::LDAP::Filter.escape(uid)
adapter.user(adapter.config.uid, uid)
end
def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
def self.find_by_email(email, adapter)
email_fields = adapter.config.attributes['email']
adapter.user(email_fields, email)
end
def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
def self.ldap_attributes(config)
[
'dn',
config.uid,
*config.attributes['name'],
*config.attributes['email'],
*config.attributes['username']
].compact.uniq
end
def self.normalize_dn(dn)
::Gitlab::Auth::LDAP::DN.new(dn).to_normalized_s
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
dn
end
# Returns the UID in a normalized form.
#
# 1. Excess spaces are stripped
# 2. The string is downcased (for case-insensitivity)
def self.normalize_uid(uid)
::Gitlab::Auth::LDAP::DN.normalize_value(uid)
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")
uid
end
def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
@provider = provider
end
def name
attribute_value(:name).first
end
def uid
entry.public_send(config.uid).first # rubocop:disable GitlabSecurity/PublicSend
end
def username
username = attribute_value(:username)
# Depending on the attribute, multiple values may
# be returned. We need only one for username.
# Ex. `uid` returns only one value but `mail` may
# return an array of multiple email addresses.
[username].flatten.first.tap do |username|
username.downcase! if config.lowercase_usernames
end
end
def email
attribute_value(:email)
end
def dn
self.class.normalize_dn(entry.dn)
end
private
def entry
@entry
end
def config
@config ||= Gitlab::Auth::LDAP::Config.new(provider)
end
# Using the LDAP attributes configuration, find and return the first
# attribute with a value. For example, by default, when given 'email',
# this method looks for 'mail', 'email' and 'userPrincipalName' and
# returns the first with a value.
def attribute_value(attribute)
attributes = Array(config.attributes[attribute.to_s])
selected_attr = attributes.find { |attr| entry.respond_to?(attr) }
return nil unless selected_attr
entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
end
# LDAP extension for User model
#
# * Find or create user from omniauth.auth data
# * Links LDAP account with existing user
# * Auth LDAP user with login and password
#
module Gitlab
module Auth
module LDAP
class User < Gitlab::Auth::OAuth::User
class << self
def find_by_uid_and_provider(uid, provider)
identity = ::Identity.with_extern_uid(provider, uid).take
identity && identity.user
end
end
def save
super('LDAP')
end
# instance methods
def find_user
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
def changed?
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
def block_after_signup?
ldap_config.block_auto_created_users
end
def allowed?
Gitlab::Auth::LDAP::Access.allowed?(gl_user)
end
def ldap_config
Gitlab::Auth::LDAP::Config.new(auth_hash.provider)
end
def auth_hash=(auth_hash)
@auth_hash = Gitlab::Auth::LDAP::AuthHash.new(auth_hash)
end
end
end
end
end
# Class to parse and transform the info provided by omniauth
#
module Gitlab
module Auth
module OAuth
class AuthHash
attr_reader :auth_hash
def initialize(auth_hash)
@auth_hash = auth_hash
end
def uid
@uid ||= Gitlab::Utils.force_utf8(auth_hash.uid.to_s)
end
def provider
@provider ||= auth_hash.provider.to_s
end
def name
@name ||= get_info(:name) || "#{get_info(:first_name)} #{get_info(:last_name)}"
end
def username
@username ||= username_and_email[:username].to_s
end
def email
@email ||= username_and_email[:email].to_s
end
def password
@password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase)
end
def location
location = get_info(:address)
if location.is_a?(Hash)
[location.locality.presence, location.country.presence].compact.join(', ')
else
location
end
end
def has_attribute?(attribute)
if attribute == :location
get_info(:address).present?
else
get_info(attribute).present?
end
end
private
def info
auth_hash.info
end
def get_info(key)
value = info[key]
Gitlab::Utils.force_utf8(value) if value
value
end
def username_and_email
@username_and_email ||= begin
username = get_info(:username).presence || get_info(:nickname).presence
email = get_info(:email).presence
username ||= generate_username(email) if email
email ||= generate_temporarily_email(username) if username
{
username: username,
email: email
}
end
end
# Get the first part of the email address (before @)
# In addtion in removes illegal characters
def generate_username(email)
email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/, '').to_s
end
def generate_temporarily_email(username)
"temp-email-for-oauth-#{username}@gitlab.localhost"
end
end
end
end
end
module Gitlab
module Auth
module OAuth
class Provider
LABELS = {
"github" => "GitHub",
"gitlab" => "GitLab.com",
"google_oauth2" => "Google"
}.freeze
def self.providers
Devise.omniauth_providers
end
def self.enabled?(name)
providers.include?(name.to_sym)
end
def self.ldap_provider?(name)
name.to_s.start_with?('ldap')
end
def self.sync_profile_from_provider?(provider)
return true if ldap_provider?(provider)
providers = Gitlab.config.omniauth.sync_profile_from_provider
if providers.is_a?(Array)
providers.include?(provider)
else
providers
end
end
def self.config_for(name)
name = name.to_s
if ldap_provider?(name)
if Gitlab::Auth::LDAP::Config.valid_provider?(name)
Gitlab::Auth::LDAP::Config.new(name).options
else
nil
end
else
Gitlab.config.omniauth.providers.find { |provider| provider.name == name }
end
end
def self.label_for(name)
name = name.to_s
config = config_for(name)
(config && config['label']) || LABELS[name] || name.titleize
end
end
end
end
end
# :nocov:
module Gitlab
module Auth
module OAuth
module Session
def self.create(provider, ticket)
Rails.cache.write("gitlab:#{provider}:#{ticket}", ticket, expires_in: Gitlab.config.omniauth.cas3.session_duration)
end
def self.destroy(provider, ticket)
Rails.cache.delete("gitlab:#{provider}:#{ticket}")
end
def self.valid?(provider, ticket)
Rails.cache.read("gitlab:#{provider}:#{ticket}").present?
end
end
end
end
end
# :nocov:
# OAuth extension for User model
#
# * Find GitLab user based on omniauth uid and provider
# * Create new user from omniauth data
#
module Gitlab
module Auth
module OAuth
class User
SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError)
attr_accessor :auth_hash, :gl_user
def initialize(auth_hash)
self.auth_hash = auth_hash
update_profile
add_or_update_user_identities
end
def persisted?
gl_user.try(:persisted?)
end
def new?
!persisted?
end
def valid?
gl_user.try(:valid?)
end
def save(provider = 'OAuth')
raise SigninDisabledForProviderError if oauth_provider_disabled?
raise SignupDisabledError unless gl_user
block_after_save = needs_blocking?
Users::UpdateService.new(gl_user, user: gl_user).execute!
gl_user.block if block_after_save
log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
log.info "(#{provider}) Error saving user #{auth_hash.uid} (#{auth_hash.email}): #{gl_user.errors.full_messages}"
return self, e.record.errors
end
def gl_user
return @gl_user if defined?(@gl_user)
@gl_user = find_user
end
def find_user
user = find_by_uid_and_provider
user ||= find_or_build_ldap_user if auto_link_ldap_user?
user ||= build_new_user if signup_enabled?
user.external = true if external_provider? && user&.new_record?
user
end
protected
def add_or_update_user_identities
return unless gl_user
# find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider }
identity ||= gl_user.identities.build(provider: auth_hash.provider)
identity.extern_uid = auth_hash.uid
if auto_link_ldap_user? && !gl_user.ldap_user? && ldap_person
log.info "Correct LDAP account has been found. identity to user: #{gl_user.username}."
gl_user.identities.build(provider: ldap_person.provider, extern_uid: ldap_person.dn)
end
end
def find_or_build_ldap_user
return unless ldap_person
user = Gitlab::Auth::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
if user
log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
return user
end
log.info "No user found using #{auth_hash.provider} provider. Creating a new one."
build_new_user
end
def find_by_email
return unless auth_hash.has_attribute?(:email)
::User.find_by(email: auth_hash.email.downcase)
end
def auto_link_ldap_user?
Gitlab.config.omniauth.auto_link_ldap_user
end
def creating_linked_ldap_user?
auto_link_ldap_user? && ldap_person
end
def ldap_person
return @ldap_person if defined?(@ldap_person)
# Look for a corresponding person with same uid in any of the configured LDAP providers
Gitlab::Auth::LDAP::Config.providers.each do |provider|
adapter = Gitlab::Auth::LDAP::Adapter.new(provider)
@ldap_person = find_ldap_person(auth_hash, adapter)
break if @ldap_person
end
@ldap_person
end
def find_ldap_person(auth_hash, adapter)
Gitlab::Auth::LDAP::Person.find_by_uid(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_email(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
end
def ldap_config
Gitlab::Auth::LDAP::Config.new(ldap_person.provider) if ldap_person
end
def needs_blocking?
new? && block_after_signup?
end
def signup_enabled?
providers = Gitlab.config.omniauth.allow_single_sign_on
if providers.is_a?(Array)
providers.include?(auth_hash.provider)
else
providers
end
end
def external_provider?
Gitlab.config.omniauth.external_providers.include?(auth_hash.provider)
end
def block_after_signup?
if creating_linked_ldap_user?
ldap_config.block_auto_created_users
else
Gitlab.config.omniauth.block_auto_created_users
end
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
def find_by_uid_and_provider
identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity && identity.user
end
def build_new_user
user_params = user_attributes.merge(skip_confirmation: true)
Users::BuildService.new(nil, user_params).execute(skip_authorization: true)
end
def user_attributes
# Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user?
username = ldap_person.username.presence
email = ldap_person.email.first.presence
end
username ||= auth_hash.username
email ||= auth_hash.email
valid_username = ::Namespace.clean_path(username)
uniquify = Uniquify.new
valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
name = auth_hash.name
name = valid_username if name.strip.empty?
{
name: name,
username: valid_username,
email: email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_automatically_set: true
}
end
def sync_profile_from_provider?
Gitlab::Auth::OAuth::Provider.sync_profile_from_provider?(auth_hash.provider)
end
def update_profile
clear_user_synced_attributes_metadata
return unless sync_profile_from_provider? || creating_linked_ldap_user?
metadata = gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider?
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user[key] = auth_hash.public_send(key) # rubocop:disable GitlabSecurity/PublicSend
metadata.set_attribute_synced(key, true)
else
metadata.set_attribute_synced(key, false)
end
end
metadata.provider = auth_hash.provider
end
if creating_linked_ldap_user? && gl_user.email == ldap_person.email.first
metadata.set_attribute_synced(:email, true)
metadata.provider = ldap_person.provider
end
end
def clear_user_synced_attributes_metadata
gl_user&.user_synced_attributes_metadata&.destroy
end
def log
Gitlab::AppLogger
end
def oauth_provider_disabled?
Gitlab::CurrentSettings.current_application_settings
.disabled_oauth_sign_in_sources
.include?(auth_hash.provider)
end
end
end
end
end
module Gitlab
module Auth
module Saml
class AuthHash < Gitlab::Auth::OAuth::AuthHash
def groups
Array.wrap(get_raw(Gitlab::Auth::Saml::Config.groups))
end
private
def get_raw(key)
# Needs to call `all` because of https://git.io/vVo4u
# otherwise just the first value is returned
auth_hash.extra[:raw_info].all[key]
end
end
end
end
end
module Gitlab
module Auth
module Saml
class Config
class << self
def options
Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
end
def groups
options[:groups_attribute]
end
def external_groups
options[:external_groups]
end
end
end
end
end
end
# SAML extension for User model
#
# * Find GitLab user based on SAML uid and provider
# * Create new user from SAML data
#
module Gitlab
module Auth
module Saml
class User < Gitlab::Auth::OAuth::User
def save
super('SAML')
end
def find_user
user = find_by_uid_and_provider
user ||= find_by_email if auto_link_saml_user?
user ||= find_or_build_ldap_user if auto_link_ldap_user?
user ||= build_new_user if signup_enabled?
if external_users_enabled? && user
# Check if there is overlap between the user's groups and the external groups
# setting then set user as external or internal.
user.external = !(auth_hash.groups & Gitlab::Auth::Saml::Config.external_groups).empty?
end
user
end
def changed?
return true unless gl_user
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
protected
def auto_link_saml_user?
Gitlab.config.omniauth.auto_link_saml_user
end
def external_users_enabled?
!Gitlab::Auth::Saml::Config.external_groups.nil?
end
def auth_hash=(auth_hash)
@auth_hash = Gitlab::Auth::Saml::AuthHash.new(auth_hash)
end
end
end
end
end
...@@ -16,6 +16,7 @@ module Gitlab ...@@ -16,6 +16,7 @@ module Gitlab
# And if the normalize behavior is changed in the future, it must be # And if the normalize behavior is changed in the future, it must be
# accompanied by another migration. # accompanied by another migration.
module Gitlab module Gitlab
module Auth
module LDAP module LDAP
class DN class DN
FormatError = Class.new(StandardError) FormatError = Class.new(StandardError)
...@@ -295,6 +296,7 @@ module Gitlab ...@@ -295,6 +296,7 @@ module Gitlab
end end
end end
end end
end
def perform(start_id, end_id) def perform(start_id, end_id)
return unless migrate? return unless migrate?
...@@ -302,11 +304,11 @@ module Gitlab ...@@ -302,11 +304,11 @@ module Gitlab
ldap_identities = Identity.where("provider like 'ldap%'").where(id: start_id..end_id) ldap_identities = Identity.where("provider like 'ldap%'").where(id: start_id..end_id)
ldap_identities.each do |identity| ldap_identities.each do |identity|
begin begin
identity.extern_uid = Gitlab::LDAP::DN.new(identity.extern_uid).to_normalized_s identity.extern_uid = Gitlab::Auth::LDAP::DN.new(identity.extern_uid).to_normalized_s
unless identity.save unless identity.save
Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping." Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping."
end end
rescue Gitlab::LDAP::DN::FormatError => e rescue Gitlab::Auth::LDAP::DN::FormatError => e
Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping." Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping."
end end
end end
......
# LDAP authorization model
#
# * Check if we are allowed access (not blocked)
#
module Gitlab
module LDAP
class Access
attr_reader :provider, :user
def self.open(user, &block)
Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
self.open(user) do |access|
if access.allowed?
Users::UpdateService.new(user, user: user, last_credential_check_at: Time.now).execute
true
else
false
end
end
end
def initialize(user, adapter = nil)
@adapter = adapter
@user = user
@provider = user.ldap_identity.provider
end
def allowed?
if ldap_user
unless ldap_config.active_directory
unblock_user(user, 'is available again') if user.ldap_blocked?
return true
end
# Block user in GitLab if he/she was blocked in AD
if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
block_user(user, 'is disabled in Active Directory')
false
else
unblock_user(user, 'is not disabled anymore') if user.ldap_blocked?
true
end
else
# Block the user if they no longer exist in LDAP/AD
block_user(user, 'does not exist anymore')
false
end
end
def adapter
@adapter ||= Gitlab::LDAP::Adapter.new(provider)
end
def ldap_config
Gitlab::LDAP::Config.new(provider)
end
def ldap_user
@ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
end
def block_user(user, reason)
user.ldap_block
Gitlab::AppLogger.info(
"LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \
"blocking Gitlab user \"#{user.name}\" (#{user.email})"
)
end
def unblock_user(user, reason)
user.activate
Gitlab::AppLogger.info(
"LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \
"unblocking Gitlab user \"#{user.name}\" (#{user.email})"
)
end
end
end
end
module Gitlab
module LDAP
class Adapter
attr_reader :provider, :ldap
def self.open(provider, &block)
Net::LDAP.open(config(provider).adapter_options) do |ldap|
block.call(self.new(provider, ldap))
end
end
def self.config(provider)
Gitlab::LDAP::Config.new(provider)
end
def initialize(provider, ldap = nil)
@provider = provider
@ldap = ldap || Net::LDAP.new(config.adapter_options)
end
def config
Gitlab::LDAP::Config.new(provider)
end
def users(fields, value, limit = nil)
options = user_options(Array(fields), value, limit)
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
Gitlab::LDAP::Person.new(entry, provider)
end
end
def user(*args)
users(*args).first
end
def dn_matches_filter?(dn, filter)
ldap_search(base: dn,
filter: filter,
scope: Net::LDAP::SearchScope_BaseObject,
attributes: %w{dn}).any?
end
def ldap_search(*args)
# Net::LDAP's `time` argument doesn't work. Use Ruby `Timeout` instead.
Timeout.timeout(config.timeout) do
results = ldap.search(*args)
if results.nil?
response = ldap.get_operation_result
unless response.code.zero?
Rails.logger.warn("LDAP search error: #{response.message}")
end
[]
else
results
end
end
rescue Net::LDAP::Error => error
Rails.logger.warn("LDAP search raised exception #{error.class}: #{error.message}")
[]
rescue Timeout::Error
Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
[]
end
private
def user_options(fields, value, limit)
options = {
attributes: Gitlab::LDAP::Person.ldap_attributes(config),
base: config.base
}
options[:size] = limit if limit
if fields.include?('dn')
raise ArgumentError, 'It is not currently possible to search the DN and other fields at the same time.' if fields.size > 1
options[:base] = value
options[:scope] = Net::LDAP::SearchScope_BaseObject
else
filter = fields.map { |field| Net::LDAP::Filter.eq(field, value) }.inject(:|)
end
options.merge(filter: user_filter(filter))
end
def user_filter(filter = nil)
user_filter = config.constructed_user_filter if config.user_filter.present?
if user_filter && filter
Net::LDAP::Filter.join(filter, user_filter)
elsif user_filter
user_filter
else
filter
end
end
end
end
end
# Class to parse and transform the info provided by omniauth
#
module Gitlab
module LDAP
class AuthHash < Gitlab::OAuth::AuthHash
def uid
@uid ||= Gitlab::LDAP::Person.normalize_dn(super)
end
def username
super.tap do |username|
username.downcase! if ldap_config.lowercase_usernames
end
end
private
def get_info(key)
attributes = ldap_config.attributes[key.to_s]
return super unless attributes
attributes = Array(attributes)
value = nil
attributes.each do |attribute|
value = get_raw(attribute)
value = value.first if value
break if value.present?
end
return super unless value
Gitlab::Utils.force_utf8(value)
value
end
def get_raw(key)
auth_hash.extra[:raw_info][key] if auth_hash.extra
end
def ldap_config
@ldap_config ||= Gitlab::LDAP::Config.new(self.provider)
end
end
end
end
# These calls help to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
# until a valid bind is found
#
module Gitlab
module LDAP
class Authentication
def self.login(login, password)
return unless Gitlab::LDAP::Config.enabled?
return unless login.present? && password.present?
auth = nil
# loop through providers until valid bind
providers.find do |provider|
auth = new(provider)
auth.login(login, password) # true will exit the loop
end
# If (login, password) was invalid for all providers, the value of auth is now the last
# Gitlab::LDAP::Authentication instance we tried.
auth.user
end
def self.providers
Gitlab::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
def login(login, password)
@ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
end
def adapter
OmniAuth::LDAP::Adaptor.new(config.omniauth_options)
end
def config
Gitlab::LDAP::Config.new(provider)
end
def user_filter(login)
filter = Net::LDAP::Filter.equals(config.uid, login)
# Apply LDAP user filter if present
if config.user_filter.present?
filter = Net::LDAP::Filter.join(filter, config.constructed_user_filter)
end
filter
end
def user
return nil unless ldap_user
Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
end
end
# Load a specific server configuration
module Gitlab
module LDAP
class Config
NET_LDAP_ENCRYPTION_METHOD = {
simple_tls: :simple_tls,
start_tls: :start_tls,
plain: nil
}.freeze
attr_accessor :provider, :options
def self.enabled?
Gitlab.config.ldap.enabled
end
def self.servers
Gitlab.config.ldap['servers']&.values || []
end
def self.available_servers
return [] unless enabled?
Array.wrap(servers.first)
end
def self.providers
servers.map { |server| server['provider_name'] }
end
def self.valid_provider?(provider)
providers.include?(provider)
end
def self.invalid_provider(provider)
raise "Unknown provider (#{provider}). Available providers: #{providers}"
end
def initialize(provider)
if self.class.valid_provider?(provider)
@provider = provider
else
self.class.invalid_provider(provider)
end
@options = config_for(@provider) # Use @provider, not provider
end
def enabled?
base_config.enabled
end
def adapter_options
opts = base_options.merge(
encryption: encryption_options
)
opts.merge!(auth_options) if has_auth?
opts
end
def omniauth_options
opts = base_options.merge(
base: base,
encryption: options['encryption'],
filter: omniauth_user_filter,
name_proc: name_proc,
disable_verify_certificates: !options['verify_certificates']
)
if has_auth?
opts.merge!(
bind_dn: options['bind_dn'],
password: options['password']
)
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts
end
def base
options['base']
end
def uid
options['uid']
end
def sync_ssh_keys?
sync_ssh_keys.present?
end
# The LDAP attribute in which the ssh keys are stored
def sync_ssh_keys
options['sync_ssh_keys']
end
def user_filter
options['user_filter']
end
def constructed_user_filter
@constructed_user_filter ||= Net::LDAP::Filter.construct(user_filter)
end
def group_base
options['group_base']
end
def admin_group
options['admin_group']
end
def active_directory
options['active_directory']
end
def block_auto_created_users
options['block_auto_created_users']
end
def attributes
default_attributes.merge(options['attributes'])
end
def timeout
options['timeout'].to_i
end
def has_auth?
options['password'] || options['bind_dn']
end
def allow_username_or_email_login
options['allow_username_or_email_login']
end
def lowercase_usernames
options['lowercase_usernames']
end
def name_proc
if allow_username_or_email_login
proc { |name| name.gsub(/@.*\z/, '') }
else
proc { |name| name }
end
end
def default_attributes
{
'username' => %w(uid sAMAccountName userid),
'email' => %w(mail email userPrincipalName),
'name' => 'cn',
'first_name' => 'givenName',
'last_name' => 'sn'
}
end
protected
def base_options
{
host: options['host'],
port: options['port']
}
end
def base_config
Gitlab.config.ldap
end
def config_for(provider)
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
def encryption_options
method = translate_method(options['encryption'])
return nil unless method
{
method: method,
tls_options: tls_options(method)
}
end
def translate_method(method_from_config)
NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
end
def tls_options(method)
return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
opts = if options['verify_certificates']
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
else
# It is important to explicitly set verify_mode for two reasons:
# 1. The behavior of OpenSSL is undefined when verify_mode is not set.
# 2. The net-ldap gem implementation verifies the certificate hostname
# unless verify_mode is set to VERIFY_NONE.
{ verify_mode: OpenSSL::SSL::VERIFY_NONE }
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts
end
def auth_options
{
auth: {
method: :simple,
username: options['bind_dn'],
password: options['password']
}
}
end
def omniauth_user_filter
uid_filter = Net::LDAP::Filter.eq(uid, '%{username}')
if user_filter.present?
Net::LDAP::Filter.join(uid_filter, constructed_user_filter).to_s
else
uid_filter.to_s
end
end
end
end
end
# -*- ruby encoding: utf-8 -*-
# Based on the `ruby-net-ldap` gem's `Net::LDAP::DN`
#
# For our purposes, this class is used to normalize DNs in order to allow proper
# comparison.
#
# E.g. DNs should be compared case-insensitively (in basically all LDAP
# implementations or setups), therefore we downcase every DN.
##
# Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
# ("Distinguished Name") is a unique identifier for an entry within an LDAP
# directory. It is made up of a number of other attributes strung together,
# to identify the entry in the tree.
#
# Each attribute that makes up a DN needs to have its value escaped so that
# the DN is valid. This class helps take care of that.
#
# A fully escaped DN needs to be unescaped when analysing its contents. This
# class also helps take care of that.
module Gitlab
module LDAP
class DN
FormatError = Class.new(StandardError)
MalformedError = Class.new(FormatError)
UnsupportedError = Class.new(FormatError)
def self.normalize_value(given_value)
dummy_dn = "placeholder=#{given_value}"
normalized_dn = new(*dummy_dn).to_normalized_s
normalized_dn.sub(/\Aplaceholder=/, '')
end
##
# Initialize a DN, escaping as required. Pass in attributes in name/value
# pairs. If there is a left over argument, it will be appended to the dn
# without escaping (useful for a base string).
#
# Most uses of this class will be to escape a DN, rather than to parse it,
# so storing the dn as an escaped String and parsing parts as required
# with a state machine seems sensible.
def initialize(*args)
if args.length > 1
initialize_array(args)
else
initialize_string(args[0])
end
end
##
# Parse a DN into key value pairs using ASN from
# http://tools.ietf.org/html/rfc2253 section 3.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def each_pair
state = :key
key = StringIO.new
value = StringIO.new
hex_buffer = ""
@dn.each_char.with_index do |char, dn_index|
case state
when :key then
case char
when 'a'..'z', 'A'..'Z' then
state = :key_normal
key << char
when '0'..'9' then
state = :key_oid
key << char
when ' ' then state = :key
else raise(MalformedError, "Unrecognized first character of an RDN attribute type name \"#{char}\"")
end
when :key_normal then
case char
when '=' then state = :value
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
else raise(MalformedError, "Unrecognized RDN attribute type name character \"#{char}\"")
end
when :key_oid then
case char
when '=' then state = :value
when '0'..'9', '.', ' ' then key << char
else raise(MalformedError, "Unrecognized RDN OID attribute type name character \"#{char}\"")
end
when :value then
case char
when '\\' then state = :value_normal_escape
when '"' then state = :value_quoted
when ' ' then state = :value
when '#' then
state = :value_hexstring
value << char
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else
state = :value_normal
value << char
end
when :value_normal then
case char
when '\\' then state = :value_normal_escape
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
when '+' then raise(UnsupportedError, "Multivalued RDNs are not supported")
else value << char
end
when :value_normal_escape then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal_escape_hex
hex_buffer = char
else
state = :value_normal
value << char
end
when :value_normal_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise(MalformedError, "Invalid escaped hex code \"\\#{hex_buffer}#{char}\"")
end
when :value_quoted then
case char
when '\\' then state = :value_quoted_escape
when '"' then state = :value_end
else value << char
end
when :value_quoted_escape then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted_escape_hex
hex_buffer = char
else
state = :value_quoted
value << char
end
when :value_quoted_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise(MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"#{char}\"")
end
when :value_hexstring then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring_hex
value << char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else raise(MalformedError, "Expected the first character of a hex pair, but got \"#{char}\"")
end
when :value_hexstring_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring
value << char
else raise(MalformedError, "Expected the second character of a hex pair, but got \"#{char}\"")
end
when :value_end then
case char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, rstrip_except_escaped(value.string, dn_index)
key = StringIO.new
value = StringIO.new
else raise(MalformedError, "Expected the end of an attribute value, but got \"#{char}\"")
end
else raise "Fell out of state machine"
end
end
# Last pair
raise(MalformedError, 'DN string ended unexpectedly') unless
[:value, :value_normal, :value_hexstring, :value_end].include? state
yield key.string.strip, rstrip_except_escaped(value.string, @dn.length)
end
def rstrip_except_escaped(str, dn_index)
str_ends_with_whitespace = str.match(/\s\z/)
if str_ends_with_whitespace
dn_part_ends_with_escaped_whitespace = @dn[0, dn_index].match(/\\(\s+)\z/)
if dn_part_ends_with_escaped_whitespace
dn_part_rwhitespace = dn_part_ends_with_escaped_whitespace[1]
num_chars_to_remove = dn_part_rwhitespace.length - 1
str = str[0, str.length - num_chars_to_remove]
else
str.rstrip!
end
end
str
end
##
# Returns the DN as an array in the form expected by the constructor.
def to_a
a = []
self.each_pair { |key, value| a << key << value } unless @dn.empty?
a
end
##
# Return the DN as an escaped string.
def to_s
@dn
end
##
# Return the DN as an escaped and normalized string.
def to_normalized_s
self.class.new(*to_a).to_s.downcase
end
# https://tools.ietf.org/html/rfc4514 section 2.4 lists these exceptions
# for DN values. All of the following must be escaped in any normal string
# using a single backslash ('\') as escape. The space character is left
# out here because in a "normalized" string, spaces should only be escaped
# if necessary (i.e. leading or trailing space).
NORMAL_ESCAPES = [',', '+', '"', '\\', '<', '>', ';', '='].freeze
# The following must be represented as escaped hex
HEX_ESCAPES = {
"\n" => '\0a',
"\r" => '\0d'
}.freeze
# Compiled character class regexp using the keys from the above hash, and
# checking for a space or # at the start, or space at the end, of the
# string.
ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
NORMAL_ESCAPES.map { |e| Regexp.escape(e) }.join +
"])")
HEX_ESCAPE_RE = Regexp.new("([" +
HEX_ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
"])")
##
# Escape a string for use in a DN value
def self.escape(string)
escaped = string.gsub(ESCAPE_RE) { |char| "\\" + char }
escaped.gsub(HEX_ESCAPE_RE) { |char| HEX_ESCAPES[char] }
end
private
def initialize_array(args)
buffer = StringIO.new
args.each_with_index do |arg, index|
if index.even? # key
buffer << "," if index > 0
buffer << arg
else # value
buffer << "="
buffer << self.class.escape(arg)
end
end
@dn = buffer.string
end
def initialize_string(arg)
@dn = arg.to_s
end
##
# Proxy all other requests to the string object, because a DN is mainly
# used within the library as a string
# rubocop:disable GitlabSecurity/PublicSend
def method_missing(method, *args, &block)
@dn.send(method, *args, &block)
end
##
# Redefined to be consistent with redefined `method_missing` behavior
def respond_to?(sym, include_private = false)
@dn.respond_to?(sym, include_private)
end
end
end
end
module Gitlab
module LDAP
class Person
# Active Directory-specific LDAP filter that checks if bit 2 of the
# userAccountControl attribute is set.
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
InvalidEntryError = Class.new(StandardError)
attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter)
uid = Net::LDAP::Filter.escape(uid)
adapter.user(adapter.config.uid, uid)
end
def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
def self.find_by_email(email, adapter)
email_fields = adapter.config.attributes['email']
adapter.user(email_fields, email)
end
def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
def self.ldap_attributes(config)
[
'dn',
config.uid,
*config.attributes['name'],
*config.attributes['email'],
*config.attributes['username']
].compact.uniq
end
def self.normalize_dn(dn)
::Gitlab::LDAP::DN.new(dn).to_normalized_s
rescue ::Gitlab::LDAP::DN::FormatError => e
Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
dn
end
# Returns the UID in a normalized form.
#
# 1. Excess spaces are stripped
# 2. The string is downcased (for case-insensitivity)
def self.normalize_uid(uid)
::Gitlab::LDAP::DN.normalize_value(uid)
rescue ::Gitlab::LDAP::DN::FormatError => e
Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")
uid
end
def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
@provider = provider
end
def name
attribute_value(:name).first
end
def uid
entry.public_send(config.uid).first # rubocop:disable GitlabSecurity/PublicSend
end
def username
username = attribute_value(:username)
# Depending on the attribute, multiple values may
# be returned. We need only one for username.
# Ex. `uid` returns only one value but `mail` may
# return an array of multiple email addresses.
[username].flatten.first.tap do |username|
username.downcase! if config.lowercase_usernames
end
end
def email
attribute_value(:email)
end
def dn
self.class.normalize_dn(entry.dn)
end
private
def entry
@entry
end
def config
@config ||= Gitlab::LDAP::Config.new(provider)
end
# Using the LDAP attributes configuration, find and return the first
# attribute with a value. For example, by default, when given 'email',
# this method looks for 'mail', 'email' and 'userPrincipalName' and
# returns the first with a value.
def attribute_value(attribute)
attributes = Array(config.attributes[attribute.to_s])
selected_attr = attributes.find { |attr| entry.respond_to?(attr) }
return nil unless selected_attr
entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
# LDAP extension for User model
#
# * Find or create user from omniauth.auth data
# * Links LDAP account with existing user
# * Auth LDAP user with login and password
#
module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
def find_by_uid_and_provider(uid, provider)
identity = ::Identity.with_extern_uid(provider, uid).take
identity && identity.user
end
end
def save
super('LDAP')
end
# instance methods
def find_user
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
def changed?
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
def block_after_signup?
ldap_config.block_auto_created_users
end
def allowed?
Gitlab::LDAP::Access.allowed?(gl_user)
end
def ldap_config
Gitlab::LDAP::Config.new(auth_hash.provider)
end
def auth_hash=(auth_hash)
@auth_hash = Gitlab::LDAP::AuthHash.new(auth_hash)
end
end
end
end
module Gitlab
module OAuth
SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError)
end
end
# Class to parse and transform the info provided by omniauth
#
module Gitlab
module OAuth
class AuthHash
attr_reader :auth_hash
def initialize(auth_hash)
@auth_hash = auth_hash
end
def uid
@uid ||= Gitlab::Utils.force_utf8(auth_hash.uid.to_s)
end
def provider
@provider ||= auth_hash.provider.to_s
end
def name
@name ||= get_info(:name) || "#{get_info(:first_name)} #{get_info(:last_name)}"
end
def username
@username ||= username_and_email[:username].to_s
end
def email
@email ||= username_and_email[:email].to_s
end
def password
@password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase)
end
def location
location = get_info(:address)
if location.is_a?(Hash)
[location.locality.presence, location.country.presence].compact.join(', ')
else
location
end
end
def has_attribute?(attribute)
if attribute == :location
get_info(:address).present?
else
get_info(attribute).present?
end
end
private
def info
auth_hash.info
end
def get_info(key)
value = info[key]
Gitlab::Utils.force_utf8(value) if value
value
end
def username_and_email
@username_and_email ||= begin
username = get_info(:username).presence || get_info(:nickname).presence
email = get_info(:email).presence
username ||= generate_username(email) if email
email ||= generate_temporarily_email(username) if username
{
username: username,
email: email
}
end
end
# Get the first part of the email address (before @)
# In addtion in removes illegal characters
def generate_username(email)
email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/, '').to_s
end
def generate_temporarily_email(username)
"temp-email-for-oauth-#{username}@gitlab.localhost"
end
end
end
end
module Gitlab
module OAuth
class Provider
LABELS = {
"github" => "GitHub",
"gitlab" => "GitLab.com",
"google_oauth2" => "Google"
}.freeze
def self.providers
Devise.omniauth_providers
end
def self.enabled?(name)
providers.include?(name.to_sym)
end
def self.ldap_provider?(name)
name.to_s.start_with?('ldap')
end
def self.sync_profile_from_provider?(provider)
return true if ldap_provider?(provider)
providers = Gitlab.config.omniauth.sync_profile_from_provider
if providers.is_a?(Array)
providers.include?(provider)
else
providers
end
end
def self.config_for(name)
name = name.to_s
if ldap_provider?(name)
if Gitlab::LDAP::Config.valid_provider?(name)
Gitlab::LDAP::Config.new(name).options
else
nil
end
else
Gitlab.config.omniauth.providers.find { |provider| provider.name == name }
end
end
def self.label_for(name)
name = name.to_s
config = config_for(name)
(config && config['label']) || LABELS[name] || name.titleize
end
end
end
end
# :nocov:
module Gitlab
module OAuth
module Session
def self.create(provider, ticket)
Rails.cache.write("gitlab:#{provider}:#{ticket}", ticket, expires_in: Gitlab.config.omniauth.cas3.session_duration)
end
def self.destroy(provider, ticket)
Rails.cache.delete("gitlab:#{provider}:#{ticket}")
end
def self.valid?(provider, ticket)
Rails.cache.read("gitlab:#{provider}:#{ticket}").present?
end
end
end
end
# :nocov:
# OAuth extension for User model
#
# * Find GitLab user based on omniauth uid and provider
# * Create new user from omniauth data
#
module Gitlab
module OAuth
class User
attr_accessor :auth_hash, :gl_user
def initialize(auth_hash)
self.auth_hash = auth_hash
update_profile
add_or_update_user_identities
end
def persisted?
gl_user.try(:persisted?)
end
def new?
!persisted?
end
def valid?
gl_user.try(:valid?)
end
def save(provider = 'OAuth')
raise SigninDisabledForProviderError if oauth_provider_disabled?
raise SignupDisabledError unless gl_user
block_after_save = needs_blocking?
Users::UpdateService.new(gl_user, user: gl_user).execute!
gl_user.block if block_after_save
log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
log.info "(#{provider}) Error saving user #{auth_hash.uid} (#{auth_hash.email}): #{gl_user.errors.full_messages}"
return self, e.record.errors
end
def gl_user
return @gl_user if defined?(@gl_user)
@gl_user = find_user
end
def find_user
user = find_by_uid_and_provider
user ||= find_or_build_ldap_user if auto_link_ldap_user?
user ||= build_new_user if signup_enabled?
user.external = true if external_provider? && user&.new_record?
user
end
protected
def add_or_update_user_identities
return unless gl_user
# find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider }
identity ||= gl_user.identities.build(provider: auth_hash.provider)
identity.extern_uid = auth_hash.uid
if auto_link_ldap_user? && !gl_user.ldap_user? && ldap_person
log.info "Correct LDAP account has been found. identity to user: #{gl_user.username}."
gl_user.identities.build(provider: ldap_person.provider, extern_uid: ldap_person.dn)
end
end
def find_or_build_ldap_user
return unless ldap_person
user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
if user
log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
return user
end
log.info "No user found using #{auth_hash.provider} provider. Creating a new one."
build_new_user
end
def find_by_email
return unless auth_hash.has_attribute?(:email)
::User.find_by(email: auth_hash.email.downcase)
end
def auto_link_ldap_user?
Gitlab.config.omniauth.auto_link_ldap_user
end
def creating_linked_ldap_user?
auto_link_ldap_user? && ldap_person
end
def ldap_person
return @ldap_person if defined?(@ldap_person)
# Look for a corresponding person with same uid in any of the configured LDAP providers
Gitlab::LDAP::Config.providers.each do |provider|
adapter = Gitlab::LDAP::Adapter.new(provider)
@ldap_person = find_ldap_person(auth_hash, adapter)
break if @ldap_person
end
@ldap_person
end
def find_ldap_person(auth_hash, adapter)
Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) ||
Gitlab::LDAP::Person.find_by_email(auth_hash.uid, adapter) ||
Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
end
def ldap_config
Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
end
def needs_blocking?
new? && block_after_signup?
end
def signup_enabled?
providers = Gitlab.config.omniauth.allow_single_sign_on
if providers.is_a?(Array)
providers.include?(auth_hash.provider)
else
providers
end
end
def external_provider?
Gitlab.config.omniauth.external_providers.include?(auth_hash.provider)
end
def block_after_signup?
if creating_linked_ldap_user?
ldap_config.block_auto_created_users
else
Gitlab.config.omniauth.block_auto_created_users
end
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
def find_by_uid_and_provider
identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity && identity.user
end
def build_new_user
user_params = user_attributes.merge(skip_confirmation: true)
Users::BuildService.new(nil, user_params).execute(skip_authorization: true)
end
def user_attributes
# Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user?
username = ldap_person.username.presence
email = ldap_person.email.first.presence
end
username ||= auth_hash.username
email ||= auth_hash.email
valid_username = ::Namespace.clean_path(username)
uniquify = Uniquify.new
valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
name = auth_hash.name
name = valid_username if name.strip.empty?
{
name: name,
username: valid_username,
email: email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_automatically_set: true
}
end
def sync_profile_from_provider?
Gitlab::OAuth::Provider.sync_profile_from_provider?(auth_hash.provider)
end
def update_profile
clear_user_synced_attributes_metadata
return unless sync_profile_from_provider? || creating_linked_ldap_user?
metadata = gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider?
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user[key] = auth_hash.public_send(key) # rubocop:disable GitlabSecurity/PublicSend
metadata.set_attribute_synced(key, true)
else
metadata.set_attribute_synced(key, false)
end
end
metadata.provider = auth_hash.provider
end
if creating_linked_ldap_user? && gl_user.email == ldap_person.email.first
metadata.set_attribute_synced(:email, true)
metadata.provider = ldap_person.provider
end
end
def clear_user_synced_attributes_metadata
gl_user&.user_synced_attributes_metadata&.destroy
end
def log
Gitlab::AppLogger
end
def oauth_provider_disabled?
Gitlab::CurrentSettings.current_application_settings
.disabled_oauth_sign_in_sources
.include?(auth_hash.provider)
end
end
end
end
module Gitlab
module Saml
class AuthHash < Gitlab::OAuth::AuthHash
def groups
Array.wrap(get_raw(Gitlab::Saml::Config.groups))
end
private
def get_raw(key)
# Needs to call `all` because of https://git.io/vVo4u
# otherwise just the first value is returned
auth_hash.extra[:raw_info].all[key]
end
end
end
end
module Gitlab
module Saml
class Config
class << self
def options
Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
end
def groups
options[:groups_attribute]
end
def external_groups
options[:external_groups]
end
end
end
end
end
# 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 find_user
user = find_by_uid_and_provider
user ||= find_by_email if auto_link_saml_user?
user ||= find_or_build_ldap_user if auto_link_ldap_user?
user ||= build_new_user if signup_enabled?
if external_users_enabled? && user
# Check if there is overlap between the user's groups and the external groups
# setting then set user as external or internal.
user.external = !(auth_hash.groups & Gitlab::Saml::Config.external_groups).empty?
end
user
end
def changed?
return true unless gl_user
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
protected
def auto_link_saml_user?
Gitlab.config.omniauth.auto_link_saml_user
end
def external_users_enabled?
!Gitlab::Saml::Config.external_groups.nil?
end
def auth_hash=(auth_hash)
@auth_hash = Gitlab::Saml::AuthHash.new(auth_hash)
end
end
end
end
...@@ -31,7 +31,7 @@ module Gitlab ...@@ -31,7 +31,7 @@ module Gitlab
return false unless can_access_git? return false unless can_access_git?
if user.requires_ldap_check? && user.try_obtain_ldap_lease if user.requires_ldap_check? && user.try_obtain_ldap_lease
return false unless Gitlab::LDAP::Access.allowed?(user) return false unless Gitlab::Auth::LDAP::Access.allowed?(user)
end end
true true
......
...@@ -336,7 +336,7 @@ namespace :gitlab do ...@@ -336,7 +336,7 @@ namespace :gitlab do
warn_user_is_not_gitlab warn_user_is_not_gitlab
start_checking "LDAP" start_checking "LDAP"
if Gitlab::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
check_ldap(args.limit) check_ldap(args.limit)
else else
puts 'LDAP is disabled in config/gitlab.yml' puts 'LDAP is disabled in config/gitlab.yml'
...@@ -346,13 +346,13 @@ namespace :gitlab do ...@@ -346,13 +346,13 @@ namespace :gitlab do
end end
def check_ldap(limit) def check_ldap(limit)
servers = Gitlab::LDAP::Config.providers servers = Gitlab::Auth::LDAP::Config.providers
servers.each do |server| servers.each do |server|
puts "Server: #{server}" puts "Server: #{server}"
begin begin
Gitlab::LDAP::Adapter.open(server) do |adapter| Gitlab::Auth::LDAP::Adapter.open(server) do |adapter|
check_ldap_auth(adapter) check_ldap_auth(adapter)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
......
...@@ -87,7 +87,7 @@ namespace :gitlab do ...@@ -87,7 +87,7 @@ namespace :gitlab do
print "#{user.name} (#{user.ldap_identity.extern_uid}) ..." print "#{user.name} (#{user.ldap_identity.extern_uid}) ..."
if Gitlab::LDAP::Access.allowed?(user) if Gitlab::Auth::LDAP::Access.allowed?(user)
puts " [OK]".color(:green) puts " [OK]".color(:green)
else else
if block_flag if block_flag
......
...@@ -382,7 +382,7 @@ describe "Admin::Users" do ...@@ -382,7 +382,7 @@ describe "Admin::Users" do
describe 'update user identities' do describe 'update user identities' do
before do before do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated]) allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
end end
it 'modifies twitter identity' do it 'modifies twitter identity' do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::Access do describe Gitlab::Auth::LDAP::Access do
let(:access) { described_class.new user } let(:access) { described_class.new user }
let(:user) { create(:omniauth_user) } let(:user) { create(:omniauth_user) }
...@@ -19,7 +19,7 @@ describe Gitlab::LDAP::Access do ...@@ -19,7 +19,7 @@ describe Gitlab::LDAP::Access do
context 'when the user cannot be found' do context 'when the user cannot be found' do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
...@@ -33,12 +33,12 @@ describe Gitlab::LDAP::Access do ...@@ -33,12 +33,12 @@ describe Gitlab::LDAP::Access do
context 'when the user is found' do context 'when the user is found' do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user)
end end
context 'and the user is disabled via active directory' do context 'and the user is disabled via active directory' do
before do before do
allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true) allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
...@@ -52,7 +52,7 @@ describe Gitlab::LDAP::Access do ...@@ -52,7 +52,7 @@ describe Gitlab::LDAP::Access do
context 'and has no disabled flag in active diretory' do context 'and has no disabled flag in active diretory' do
before do before do
allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -87,15 +87,15 @@ describe Gitlab::LDAP::Access do ...@@ -87,15 +87,15 @@ describe Gitlab::LDAP::Access do
context 'without ActiveDirectory enabled' do context 'without ActiveDirectory enabled' do
before do before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
allow_any_instance_of(Gitlab::LDAP::Config).to receive(:active_directory).and_return(false) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:active_directory).and_return(false)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
context 'when user cannot be found' do context 'when user cannot be found' do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::Adapter do describe Gitlab::Auth::LDAP::Adapter do
include LdapHelpers include LdapHelpers
let(:ldap) { double(:ldap) } let(:ldap) { double(:ldap) }
...@@ -139,6 +139,6 @@ describe Gitlab::LDAP::Adapter do ...@@ -139,6 +139,6 @@ describe Gitlab::LDAP::Adapter do
end end
def ldap_attributes def ldap_attributes
Gitlab::LDAP::Person.ldap_attributes(Gitlab::LDAP::Config.new('ldapmain')) Gitlab::Auth::LDAP::Person.ldap_attributes(Gitlab::Auth::LDAP::Config.new('ldapmain'))
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::AuthHash do describe Gitlab::Auth::LDAP::AuthHash do
include LdapHelpers include LdapHelpers
let(:auth_hash) do let(:auth_hash) do
...@@ -56,7 +56,7 @@ describe Gitlab::LDAP::AuthHash do ...@@ -56,7 +56,7 @@ describe Gitlab::LDAP::AuthHash do
end end
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive(:attributes).and_return(attributes) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:attributes).and_return(attributes)
end end
it "has the correct username" do it "has the correct username" do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::Authentication do describe Gitlab::Auth::LDAP::Authentication do
let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' } 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(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::LDAP::Person.normalize_dn(dn)) }
let(:login) { 'john' } let(:login) { 'john' }
let(:password) { 'password' } let(:password) { 'password' }
describe 'login' do describe 'login' do
before 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 end
it "finds the user if authentication is successful" do it "finds the user if authentication is successful" do
...@@ -43,7 +43,7 @@ describe Gitlab::LDAP::Authentication do ...@@ -43,7 +43,7 @@ describe Gitlab::LDAP::Authentication do
end end
it "fails if ldap is disabled" do it "fails if ldap is disabled" do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(false) allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(false)
expect(described_class.login(login, password)).to be_falsey expect(described_class.login(login, password)).to be_falsey
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::Config do describe Gitlab::Auth::LDAP::Config do
include LdapHelpers include LdapHelpers
let(:config) { described_class.new('ldapmain') } let(:config) { described_class.new('ldapmain') }
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::DN do describe Gitlab::Auth::LDAP::DN do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
describe '#normalize_value' do describe '#normalize_value' do
...@@ -13,7 +13,7 @@ describe Gitlab::LDAP::DN do ...@@ -13,7 +13,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'John Smith,' } let(:given) { 'John Smith,' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -21,7 +21,7 @@ describe Gitlab::LDAP::DN do ...@@ -21,7 +21,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '#aa aa' } let(:given) { '#aa aa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
end end
end end
...@@ -29,7 +29,7 @@ describe Gitlab::LDAP::DN do ...@@ -29,7 +29,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '#aaXaaa' } let(:given) { '#aaXaaa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
end end
end end
...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::DN do ...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '#aaaYaa' } let(:given) { '#aaaYaa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
end end
end end
...@@ -45,7 +45,7 @@ describe Gitlab::LDAP::DN do ...@@ -45,7 +45,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '"Sebasti\\cX\\a1n"' } let(:given) { '"Sebasti\\cX\\a1n"' }
it 'raises MalformedError' do 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\"") 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
end end
...@@ -53,7 +53,7 @@ describe Gitlab::LDAP::DN do ...@@ -53,7 +53,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '"James' } let(:given) { '"James' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -61,7 +61,7 @@ describe Gitlab::LDAP::DN do ...@@ -61,7 +61,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'J\ames' } let(:given) { 'J\ames' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
end end
end end
...@@ -69,7 +69,7 @@ describe Gitlab::LDAP::DN do ...@@ -69,7 +69,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'foo\\' } let(:given) { 'foo\\' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
end end
...@@ -86,7 +86,7 @@ describe Gitlab::LDAP::DN do ...@@ -86,7 +86,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' } let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' }
it 'raises UnsupportedError' do it 'raises UnsupportedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
end end
end end
...@@ -95,7 +95,7 @@ describe Gitlab::LDAP::DN do ...@@ -95,7 +95,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' } let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' }
it 'raises UnsupportedError' do it 'raises UnsupportedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
end end
end end
...@@ -103,7 +103,7 @@ describe Gitlab::LDAP::DN do ...@@ -103,7 +103,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' } let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' }
it 'raises UnsupportedError' do it 'raises UnsupportedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
end end
end end
end end
...@@ -115,7 +115,7 @@ describe Gitlab::LDAP::DN do ...@@ -115,7 +115,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid=John Smith,' } let(:given) { 'uid=John Smith,' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -123,7 +123,7 @@ describe Gitlab::LDAP::DN do ...@@ -123,7 +123,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' } let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
end end
end end
...@@ -131,7 +131,7 @@ describe Gitlab::LDAP::DN do ...@@ -131,7 +131,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' } let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
end end
end end
...@@ -139,7 +139,7 @@ describe Gitlab::LDAP::DN do ...@@ -139,7 +139,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' } let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
end end
end end
...@@ -147,7 +147,7 @@ describe Gitlab::LDAP::DN do ...@@ -147,7 +147,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid="Sebasti\\cX\\a1n"' } let(:given) { 'uid="Sebasti\\cX\\a1n"' }
it 'raises MalformedError' do 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\"") 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
end end
...@@ -155,7 +155,7 @@ describe Gitlab::LDAP::DN do ...@@ -155,7 +155,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'John' } let(:given) { 'John' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -163,7 +163,7 @@ describe Gitlab::LDAP::DN do ...@@ -163,7 +163,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'cn="James' } let(:given) { 'cn="James' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -171,7 +171,7 @@ describe Gitlab::LDAP::DN do ...@@ -171,7 +171,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'cn=J\ames' } let(:given) { 'cn=J\ames' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
end end
end end
...@@ -179,7 +179,7 @@ describe Gitlab::LDAP::DN do ...@@ -179,7 +179,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'cn=\\' } let(:given) { 'cn=\\' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
end end
end end
...@@ -187,7 +187,7 @@ describe Gitlab::LDAP::DN do ...@@ -187,7 +187,7 @@ describe Gitlab::LDAP::DN do
let(:given) { '1.2.d=Value' } let(:given) { '1.2.d=Value' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"')
end end
end end
...@@ -195,7 +195,7 @@ describe Gitlab::LDAP::DN do ...@@ -195,7 +195,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'd1.2=Value' } let(:given) { 'd1.2=Value' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."')
end end
end end
...@@ -203,7 +203,7 @@ describe Gitlab::LDAP::DN do ...@@ -203,7 +203,7 @@ describe Gitlab::LDAP::DN do
let(:given) { ' -uid=John Smith' } let(:given) { ' -uid=John Smith' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"')
end end
end end
...@@ -211,7 +211,7 @@ describe Gitlab::LDAP::DN do ...@@ -211,7 +211,7 @@ describe Gitlab::LDAP::DN do
let(:given) { 'uid\\=john' } let(:given) { 'uid\\=john' }
it 'raises MalformedError' do it 'raises MalformedError' do
expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"') expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"')
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::Person do describe Gitlab::Auth::LDAP::Person do
include LdapHelpers include LdapHelpers
let(:entry) { ldap_user_entry('john.doe') } let(:entry) { ldap_user_entry('john.doe') }
...@@ -59,7 +59,7 @@ describe Gitlab::LDAP::Person do ...@@ -59,7 +59,7 @@ describe Gitlab::LDAP::Person do
} }
} }
) )
config = Gitlab::LDAP::Config.new('ldapmain') config = Gitlab::Auth::LDAP::Config.new('ldapmain')
ldap_attributes = described_class.ldap_attributes(config) ldap_attributes = described_class.ldap_attributes(config)
expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof)) expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof))
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::LDAP::User do describe Gitlab::Auth::LDAP::User do
let(:ldap_user) { described_class.new(auth_hash) } let(:ldap_user) { described_class.new(auth_hash) }
let(:gl_user) { ldap_user.gl_user } let(:gl_user) { ldap_user.gl_user }
let(:info) do let(:info) do
...@@ -177,7 +177,7 @@ describe Gitlab::LDAP::User do ...@@ -177,7 +177,7 @@ describe Gitlab::LDAP::User do
describe 'blocking' do describe 'blocking' do
def configure_block(value) def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config) allow_any_instance_of(Gitlab::Auth::LDAP::Config)
.to receive(:block_auto_created_users).and_return(value) .to receive(:block_auto_created_users).and_return(value)
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::OAuth::AuthHash do describe Gitlab::Auth::OAuth::AuthHash do
let(:provider) { 'ldap'.freeze } let(:provider) { 'ldap'.freeze }
let(:auth_hash) do let(:auth_hash) do
described_class.new( described_class.new(
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::OAuth::Provider do describe Gitlab::Auth::OAuth::Provider do
describe '#config_for' do describe '#config_for' do
context 'for an LDAP provider' do context 'for an LDAP provider' do
context 'when the provider exists' do context 'when the provider exists' do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::OAuth::User do describe Gitlab::Auth::OAuth::User do
let(:oauth_user) { described_class.new(auth_hash) } let(:oauth_user) { described_class.new(auth_hash) }
let(:gl_user) { oauth_user.gl_user } let(:gl_user) { oauth_user.gl_user }
let(:uid) { 'my-uid' } let(:uid) { 'my-uid' }
...@@ -18,7 +18,7 @@ describe Gitlab::OAuth::User do ...@@ -18,7 +18,7 @@ describe Gitlab::OAuth::User do
} }
} }
end end
let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
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') }
...@@ -39,7 +39,7 @@ describe Gitlab::OAuth::User do ...@@ -39,7 +39,7 @@ describe Gitlab::OAuth::User do
describe '#save' do describe '#save' do
def stub_ldap_config(messages) def stub_ldap_config(messages)
allow(Gitlab::LDAP::Config).to receive_messages(messages) allow(Gitlab::Auth::LDAP::Config).to receive_messages(messages)
end end
let(:provider) { 'twitter' } let(:provider) { 'twitter' }
...@@ -215,7 +215,7 @@ describe Gitlab::OAuth::User do ...@@ -215,7 +215,7 @@ describe Gitlab::OAuth::User do
context "and no account for the LDAP user" do context "and no account for the LDAP user" do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save oauth_user.save
end end
...@@ -250,7 +250,7 @@ describe Gitlab::OAuth::User do ...@@ -250,7 +250,7 @@ describe Gitlab::OAuth::User do
context "and LDAP user has an account already" do 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') } 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 it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save oauth_user.save
...@@ -270,8 +270,8 @@ describe Gitlab::OAuth::User do ...@@ -270,8 +270,8 @@ describe Gitlab::OAuth::User do
context 'when an LDAP person is not found by uid' do 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 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::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
oauth_user.save oauth_user.save
...@@ -297,7 +297,7 @@ describe Gitlab::OAuth::User do ...@@ -297,7 +297,7 @@ describe Gitlab::OAuth::User do
context 'and no account for the LDAP user' do context 'and no account for the LDAP user' do
it 'creates a user favoring the LDAP username and strips email domain' 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) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save oauth_user.save
...@@ -309,7 +309,7 @@ describe Gitlab::OAuth::User do ...@@ -309,7 +309,7 @@ describe Gitlab::OAuth::User do
context "and no corresponding LDAP person" do context "and no corresponding LDAP person" do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
end end
include_examples "to verify compliance with allow_single_sign_on" include_examples "to verify compliance with allow_single_sign_on"
...@@ -358,13 +358,13 @@ describe Gitlab::OAuth::User do ...@@ -358,13 +358,13 @@ describe Gitlab::OAuth::User do
allow(ldap_user).to receive(:username) { 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(:email) { ['johndoe@example.com', 'john2@example.com'] }
allow(ldap_user).to receive(:dn) { dn } allow(ldap_user).to receive(:dn) { dn }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
end end
context "and no account for the LDAP user" do context "and no account for the LDAP user" do
context 'dont block on create (LDAP)' do context 'dont block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false)
end end
it do it do
...@@ -376,7 +376,7 @@ describe Gitlab::OAuth::User do ...@@ -376,7 +376,7 @@ describe Gitlab::OAuth::User do
context 'block on create (LDAP)' do context 'block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true)
end end
it do it do
...@@ -392,7 +392,7 @@ describe Gitlab::OAuth::User do ...@@ -392,7 +392,7 @@ describe Gitlab::OAuth::User do
context 'dont block on create (LDAP)' do context 'dont block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false)
end end
it do it do
...@@ -404,7 +404,7 @@ describe Gitlab::OAuth::User do ...@@ -404,7 +404,7 @@ describe Gitlab::OAuth::User do
context 'block on create (LDAP)' do context 'block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true)
end end
it do it do
...@@ -448,7 +448,7 @@ describe Gitlab::OAuth::User do ...@@ -448,7 +448,7 @@ describe Gitlab::OAuth::User do
context 'dont block on create (LDAP)' do context 'dont block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false)
end end
it do it do
...@@ -460,7 +460,7 @@ describe Gitlab::OAuth::User do ...@@ -460,7 +460,7 @@ describe Gitlab::OAuth::User do
context 'block on create (LDAP)' do context 'block on create (LDAP)' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true)
end end
it do it do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Saml::AuthHash do describe Gitlab::Auth::Saml::AuthHash do
include LoginHelpers include LoginHelpers
let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } } let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } }
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Saml::User do describe Gitlab::Auth::Saml::User do
include LdapHelpers include LdapHelpers
include LoginHelpers include LoginHelpers
...@@ -17,7 +17,7 @@ describe Gitlab::Saml::User do ...@@ -17,7 +17,7 @@ describe Gitlab::Saml::User do
email: 'john@mail.com' email: 'john@mail.com'
} }
end end
let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe '#save' do describe '#save' do
before do before do
...@@ -159,10 +159,10 @@ describe Gitlab::Saml::User do ...@@ -159,10 +159,10 @@ describe Gitlab::Saml::User do
allow(ldap_user).to receive(:username) { 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(:email) { %w(john@mail.com john2@example.com) }
allow(ldap_user).to receive(:dn) { dn } allow(ldap_user).to receive(:dn) { dn }
allow(Gitlab::LDAP::Adapter).to receive(:new).and_return(adapter) allow(Gitlab::Auth::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::Auth::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::Auth::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) allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user)
end end
context 'and no account for the LDAP user' do context 'and no account for the LDAP user' do
...@@ -210,10 +210,10 @@ describe Gitlab::Saml::User do ...@@ -210,10 +210,10 @@ describe Gitlab::Saml::User do
nil_types = uid_types - [uid_type] nil_types = uid_types - [uid_type]
nil_types.each do |type| nil_types.each do |type|
allow(Gitlab::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil) allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil)
end end
allow(Gitlab::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user) allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user)
end end
it 'adds the omniauth identity to the LDAP account' do it 'adds the omniauth identity to the LDAP account' do
...@@ -280,7 +280,7 @@ describe Gitlab::Saml::User do ...@@ -280,7 +280,7 @@ describe Gitlab::Saml::User do
it 'adds the LDAP identity to the existing SAML user' 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') 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) 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_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash)
local_saml_user = described_class.new(local_hash) local_saml_user = described_class.new(local_hash)
......
...@@ -309,17 +309,17 @@ describe Gitlab::Auth do ...@@ -309,17 +309,17 @@ describe Gitlab::Auth do
context "with ldap enabled" do context "with ldap enabled" do
before 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 end
it "tries to autheticate with db before ldap" do 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) gl_auth.find_with_user_password(username, password)
end end
it "uses ldap as fallback to for authentication" do 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') gl_auth.find_with_user_password('ldap_user', 'password')
end end
...@@ -336,7 +336,7 @@ describe Gitlab::Auth do ...@@ -336,7 +336,7 @@ describe Gitlab::Auth do
context "with ldap enabled" do context "with ldap enabled" do
before 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 end
it "does not find non-ldap user by valid login/password" do it "does not find non-ldap user by valid login/password" do
......
...@@ -506,8 +506,8 @@ describe 'Git HTTP requests' do ...@@ -506,8 +506,8 @@ describe 'Git HTTP requests' do
context 'when LDAP is configured' do context 'when LDAP is configured' do
before do before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
allow_any_instance_of(Gitlab::LDAP::Authentication) allow_any_instance_of(Gitlab::Auth::LDAP::Authentication)
.to receive(:login).and_return(nil) .to receive(:login).and_return(nil)
end end
...@@ -795,9 +795,9 @@ describe 'Git HTTP requests' do ...@@ -795,9 +795,9 @@ describe 'Git HTTP requests' do
let(:path) { 'doesnt/exist.git' } let(:path) { 'doesnt/exist.git' }
before do before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
allow(Gitlab::LDAP::Authentication).to receive(:login).and_return(nil) allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user) allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
end end
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
......
module LdapHelpers module LdapHelpers
def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
::Gitlab::LDAP::Adapter.new(provider, ldap) ::Gitlab::Auth::LDAP::Adapter.new(provider, ldap)
end end
def user_dn(uid) def user_dn(uid)
"uid=#{uid},ou=users,dc=example,dc=com" "uid=#{uid},ou=users,dc=example,dc=com"
end end
# Accepts a hash of Gitlab::LDAP::Config keys and values. # Accepts a hash of Gitlab::Auth::LDAP::Config keys and values.
# #
# Example: # Example:
# stub_ldap_config( # stub_ldap_config(
...@@ -15,21 +15,21 @@ module LdapHelpers ...@@ -15,21 +15,21 @@ module LdapHelpers
# admin_group: 'my-admin-group' # admin_group: 'my-admin-group'
# ) # )
def stub_ldap_config(messages) def stub_ldap_config(messages)
allow_any_instance_of(::Gitlab::LDAP::Config).to receive_messages(messages) allow_any_instance_of(::Gitlab::Auth::LDAP::Config).to receive_messages(messages)
end end
# Stub an LDAP person search and provide the return entry. Specify `nil` for # Stub an LDAP person search and provide the return entry. Specify `nil` for
# `entry` to simulate when an LDAP person is not found # `entry` to simulate when an LDAP person is not found
# #
# Example: # Example:
# adapter = ::Gitlab::LDAP::Adapter.new('ldapmain', double(:ldap)) # adapter = ::Gitlab::Auth::LDAP::Adapter.new('ldapmain', double(:ldap))
# ldap_user_entry = ldap_user_entry('john_doe') # ldap_user_entry = ldap_user_entry('john_doe')
# #
# stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter) # stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter)
def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain') def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain')
return_value = ::Gitlab::LDAP::Person.new(entry, provider) if entry.present? return_value = ::Gitlab::Auth::LDAP::Person.new(entry, provider) if entry.present?
allow(::Gitlab::LDAP::Person) allow(::Gitlab::Auth::LDAP::Person)
.to receive(:find_by_uid).with(uid, any_args).and_return(return_value) .to receive(:find_by_uid).with(uid, any_args).and_return(return_value)
end end
......
...@@ -138,7 +138,7 @@ module LoginHelpers ...@@ -138,7 +138,7 @@ module LoginHelpers
Rails.application.routes.draw do Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml' post '/users/auth/saml' => 'omniauth_callbacks#saml'
end end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages) stub_omniauth_setting(messages)
allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
...@@ -149,10 +149,10 @@ module LoginHelpers ...@@ -149,10 +149,10 @@ module LoginHelpers
end end
def stub_basic_saml_config def stub_basic_saml_config
allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } })
end end
def stub_saml_group_config(groups) def stub_saml_group_config(groups)
allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } })
end end
end end
...@@ -11,8 +11,8 @@ describe 'gitlab:ldap:check rake task' do ...@@ -11,8 +11,8 @@ describe 'gitlab:ldap:check rake task' do
context 'when LDAP is not enabled' do context 'when LDAP is not enabled' do
it 'does not attempt to bind or search for users' do it 'does not attempt to bind or search for users' do
expect(Gitlab::LDAP::Config).not_to receive(:providers) expect(Gitlab::Auth::LDAP::Config).not_to receive(:providers)
expect(Gitlab::LDAP::Adapter).not_to receive(:open) expect(Gitlab::Auth::LDAP::Adapter).not_to receive(:open)
run_rake_task('gitlab:ldap:check') run_rake_task('gitlab:ldap:check')
end end
...@@ -23,12 +23,12 @@ describe 'gitlab:ldap:check rake task' do ...@@ -23,12 +23,12 @@ describe 'gitlab:ldap:check rake task' do
let(:adapter) { ldap_adapter('ldapmain', ldap) } let(:adapter) { ldap_adapter('ldapmain', ldap) }
before do before do
allow(Gitlab::LDAP::Config) allow(Gitlab::Auth::LDAP::Config)
.to receive_messages( .to receive_messages(
enabled?: true, enabled?: true,
providers: ['ldapmain'] providers: ['ldapmain']
) )
allow(Gitlab::LDAP::Adapter).to receive(:open).and_yield(adapter) allow(Gitlab::Auth::LDAP::Adapter).to receive(:open).and_yield(adapter)
allow(adapter).to receive(:users).and_return([]) allow(adapter).to receive(:users).and_return([])
end end
......
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