333 lines
8.4 KiB
Ruby
333 lines
8.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# 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
|
|
|
|
InvalidProvider = Class.new(StandardError)
|
|
|
|
def self.enabled?
|
|
Gitlab.config.ldap.enabled
|
|
end
|
|
|
|
def self.sign_in_enabled?
|
|
enabled? && !prevent_ldap_sign_in?
|
|
end
|
|
|
|
def self.prevent_ldap_sign_in?
|
|
Gitlab.config.ldap.prevent_ldap_sign_in
|
|
end
|
|
|
|
def self.servers
|
|
Gitlab.config.ldap.servers&.values || []
|
|
end
|
|
|
|
def self.available_servers
|
|
return [] unless enabled?
|
|
|
|
_available_servers
|
|
end
|
|
|
|
def self._available_servers
|
|
Array.wrap(servers.first)
|
|
end
|
|
|
|
def self.providers
|
|
provider_names_from_servers(servers)
|
|
end
|
|
|
|
def self.available_providers
|
|
provider_names_from_servers(available_servers)
|
|
end
|
|
|
|
def self.provider_names_from_servers(servers)
|
|
servers&.map { |server| server['provider_name'] } || []
|
|
end
|
|
private_class_method :provider_names_from_servers
|
|
|
|
def self.valid_provider?(provider)
|
|
providers.include?(provider)
|
|
end
|
|
|
|
def self.invalid_provider(provider)
|
|
raise InvalidProvider, "Unknown provider (#{provider}). Available providers: #{providers}"
|
|
end
|
|
|
|
def self.encrypted_secrets
|
|
Settings.encrypted(Gitlab.config.ldap.secret_file)
|
|
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,
|
|
instrumentation_service: ActiveSupport::Notifications
|
|
)
|
|
|
|
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'],
|
|
tls_options: tls_options
|
|
)
|
|
|
|
if has_auth?
|
|
opts.merge!(
|
|
bind_dn: auth_username,
|
|
password: auth_password
|
|
)
|
|
end
|
|
|
|
opts
|
|
end
|
|
|
|
def base
|
|
@base ||= Person.normalize_dn(options['base'])
|
|
end
|
|
|
|
def uid
|
|
options['uid']
|
|
end
|
|
|
|
def label
|
|
options['label']
|
|
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 retry_empty_result_with_codes
|
|
options.fetch('retry_empty_result_with_codes', [])
|
|
end
|
|
|
|
def external_groups
|
|
options['external_groups'] || []
|
|
end
|
|
|
|
def has_auth?
|
|
auth_password || auth_username
|
|
end
|
|
|
|
def allow_username_or_email_login
|
|
options['allow_username_or_email_login']
|
|
end
|
|
|
|
def lowercase_usernames
|
|
options['lowercase_usernames']
|
|
end
|
|
|
|
def sync_name
|
|
options['sync_name']
|
|
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} uid sAMAccountName userid).uniq,
|
|
'email' => %w(mail email userPrincipalName),
|
|
'name' => 'cn',
|
|
'first_name' => 'givenName',
|
|
'last_name' => 'sn'
|
|
}
|
|
end
|
|
|
|
protected
|
|
|
|
def base_options
|
|
{
|
|
host: options['host'],
|
|
port: options['port'],
|
|
hosts: options['hosts']
|
|
}
|
|
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
|
|
return unless method
|
|
|
|
{
|
|
method: method,
|
|
tls_options: tls_options
|
|
}
|
|
end
|
|
|
|
def translate_method
|
|
NET_LDAP_ENCRYPTION_METHOD[options['encryption']&.to_sym]
|
|
end
|
|
|
|
def tls_options
|
|
return @tls_options if defined?(@tls_options)
|
|
|
|
method = translate_method
|
|
return unless method
|
|
|
|
opts = if options['verify_certificates'] && method != 'plain'
|
|
# Dup so we don't accidentally overwrite the constant
|
|
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.dup
|
|
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.merge!(custom_tls_options)
|
|
|
|
@tls_options = opts
|
|
end
|
|
|
|
def custom_tls_options
|
|
return {} unless options['tls_options']
|
|
|
|
# Dup so we don't overwrite the original value
|
|
custom_options = options['tls_options'].dup.delete_if { |_, value| value.nil? || value.blank? }
|
|
custom_options.symbolize_keys!
|
|
|
|
if custom_options[:cert]
|
|
begin
|
|
custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert])
|
|
rescue OpenSSL::X509::CertificateError => e
|
|
Gitlab::AppLogger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}"
|
|
end
|
|
end
|
|
|
|
if custom_options[:key]
|
|
begin
|
|
custom_options[:key] = OpenSSL::PKey.read(custom_options[:key])
|
|
rescue OpenSSL::PKey::PKeyError => e
|
|
Gitlab::AppLogger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}"
|
|
end
|
|
end
|
|
|
|
custom_options
|
|
end
|
|
|
|
def auth_options
|
|
{
|
|
auth: {
|
|
method: :simple,
|
|
username: auth_username,
|
|
password: auth_password
|
|
}
|
|
}
|
|
end
|
|
|
|
def secrets
|
|
@secrets ||= self.class.encrypted_secrets[@provider.delete_prefix('ldap').to_sym]
|
|
rescue StandardError => e
|
|
Gitlab::AppLogger.error "LDAP encrypted secrets are invalid: #{e.inspect}"
|
|
|
|
nil
|
|
end
|
|
|
|
def auth_password
|
|
return options['password'] if options['password']
|
|
|
|
secrets&.fetch(:password, nil)&.chomp
|
|
end
|
|
|
|
def auth_username
|
|
return options['bind_dn'] if options['bind_dn']
|
|
|
|
secrets&.fetch(:bind_dn, nil)&.chomp
|
|
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
|
|
|
|
Gitlab::Auth::Ldap::Config.prepend_mod_with('Gitlab::Auth::Ldap::Config')
|