From c50b98da723dab9a35ddb2cde0258d141cf92495 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 11 Nov 2016 14:44:08 -0600 Subject: [PATCH] Centralize LDAP config/filter logic Centralize all LDAP config logic in `GitLab::LDAP::Config`. Previously, some logic was in the Devise initializer and it was not honoring the `user_filter`. If a user outside the configured `user_filter` signed in, an account would be created but they would then be denied access. Now that logic is centralized, the filter is honored and users outside the filter are never created. --- app/helpers/auth_helper.rb | 2 +- changelogs/unreleased/user_filter_auth.yml | 4 ++ config/initializers/devise.rb | 19 +---- lib/gitlab/ldap/adapter.rb | 4 +- lib/gitlab/ldap/authentication.rb | 6 +- lib/gitlab/ldap/config.rb | 65 +++++++++++++++-- spec/lib/gitlab/ldap/config_spec.rb | 81 ++++++++++++++++++++++ 7 files changed, 150 insertions(+), 31 deletions(-) create mode 100644 changelogs/unreleased/user_filter_auth.yml diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index cd4d778e508..92bac149313 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -3,7 +3,7 @@ module AuthHelper FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze def ldap_enabled? - Gitlab.config.ldap.enabled + Gitlab::LDAP::Config.enabled? end def omniauth_enabled? diff --git a/changelogs/unreleased/user_filter_auth.yml b/changelogs/unreleased/user_filter_auth.yml new file mode 100644 index 00000000000..e4071e22e5e --- /dev/null +++ b/changelogs/unreleased/user_filter_auth.yml @@ -0,0 +1,4 @@ +--- +title: Centralize LDAP config/filter logic +merge_request: 6606 +author: diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index a0a8f88584c..f06c4d4ecf2 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -213,22 +213,9 @@ Devise.setup do |config| end if Gitlab::LDAP::Config.enabled? - Gitlab.config.ldap.servers.values.each do |server| - if server['allow_username_or_email_login'] - email_stripping_proc = ->(name) {name.gsub(/@.*\z/, '')} - else - email_stripping_proc = ->(name) {name} - end - - config.omniauth server['provider_name'], - host: server['host'], - base: server['base'], - uid: server['uid'], - port: server['port'], - method: server['method'], - bind_dn: server['bind_dn'], - password: server['password'], - name_proc: email_stripping_proc + Gitlab::LDAP::Config.providers.each do |provider| + ldap_config = Gitlab::LDAP::Config.new(provider) + config.omniauth(provider, ldap_config.omniauth_options) end end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 8b38cfaefb6..7b05290e5cc 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -89,9 +89,7 @@ module Gitlab end def user_filter(filter = nil) - if config.user_filter.present? - user_filter = Net::LDAP::Filter.construct(config.user_filter) - end + user_filter = config.constructed_user_filter if config.user_filter.present? if user_filter && filter Net::LDAP::Filter.join(filter, user_filter) diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb index bad683c6511..4745311402c 100644 --- a/lib/gitlab/ldap/authentication.rb +++ b/lib/gitlab/ldap/authentication.rb @@ -54,11 +54,9 @@ module Gitlab # Apply LDAP user filter if present if config.user_filter.present? - filter = Net::LDAP::Filter.join( - filter, - Net::LDAP::Filter.construct(config.user_filter) - ) + filter = Net::LDAP::Filter.join(filter, config.constructed_user_filter) end + filter end diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 6ea069d26df..de52ef3fc65 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -13,7 +13,7 @@ module Gitlab end def self.providers - servers.map {|server| server['provider_name'] } + servers.map { |server| server['provider_name'] } end def self.valid_provider?(provider) @@ -38,13 +38,31 @@ module Gitlab end def adapter_options - { - host: options['host'], - port: options['port'], - encryption: encryption - }.tap do |options| - options.merge!(auth_options) if has_auth? + opts = base_options.merge( + encryption: encryption, + ) + + opts.merge!(auth_options) if has_auth? + + opts + end + + def omniauth_options + opts = base_options.merge( + base: base, + method: options['method'], + filter: omniauth_user_filter, + name_proc: name_proc + ) + + if has_auth? + opts.merge!( + bind_dn: options['bind_dn'], + password: options['password'] + ) end + + opts end def base @@ -68,6 +86,10 @@ module Gitlab 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 @@ -96,8 +118,27 @@ module Gitlab options['password'] || options['bind_dn'] end + def allow_username_or_email_login + options['allow_username_or_email_login'] + end + + def name_proc + if allow_username_or_email_login + Proc.new { |name| name.gsub(/@.*\z/, '') } + else + Proc.new { |name| name } + end + end + protected + def base_options + { + host: options['host'], + port: options['port'] + } + end + def base_config Gitlab.config.ldap end @@ -126,6 +167,16 @@ module Gitlab } } 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 diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index f5ebe703083..1a6803e01c3 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -19,6 +19,87 @@ describe Gitlab::LDAP::Config, lib: true do end end + describe '#adapter_options' do + it 'constructs basic options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 386, + 'method' => 'plain' + } + ) + + expect(config.adapter_options).to eq( + host: 'ldap.example.com', + port: 386, + encryption: nil + ) + end + + it 'includes authentication options when auth is configured' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'method' => 'ssl', + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => 'super_secret' + } + ) + + expect(config.adapter_options).to eq( + host: 'ldap.example.com', + port: 686, + encryption: :simple_tls, + auth: { + method: :simple, + username: 'uid=admin,dc=example,dc=com', + password: 'super_secret' + } + ) + end + end + + describe '#omniauth_options' do + it 'constructs basic options' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 386, + 'base' => 'ou=users,dc=example,dc=com', + 'method' => 'plain', + 'uid' => 'uid' + } + ) + + expect(config.omniauth_options).to include( + host: 'ldap.example.com', + port: 386, + base: 'ou=users,dc=example,dc=com', + method: 'plain', + filter: '(uid=%{username})' + ) + expect(config.omniauth_options.keys).not_to include(:bind_dn, :password) + end + + it 'includes authentication options when auth is configured' do + stub_ldap_config( + options: { + 'uid' => 'sAMAccountName', + 'user_filter' => '(memberOf=cn=group1,ou=groups,dc=example,dc=com)', + 'bind_dn' => 'uid=admin,dc=example,dc=com', + 'password' => 'super_secret' + } + ) + + expect(config.omniauth_options).to include( + filter: '(&(sAMAccountName=%{username})(memberOf=cn=group1,ou=groups,dc=example,dc=com))', + bind_dn: 'uid=admin,dc=example,dc=com', + password: 'super_secret' + ) + end + end + describe '#has_auth?' do it 'is true when password is set' do stub_ldap_config(