Commit 961810a4427ed452a9557b3f4bbe5fa8a1f21229

Authored by Dmitriy Zaporozhets
2 parents a107776e be1120e9

Merge branch 'ad_disabled_users' into 'master'

Block 'disabled' AD users over SSH

The existing SSH access check for LDAP users ignored the ActiveDirectory 'disabled' flag.
lib/gitlab/git_access.rb
@@ -66,8 +66,8 @@ module Gitlab @@ -66,8 +66,8 @@ module Gitlab
66 if Gitlab.config.ldap.enabled 66 if Gitlab.config.ldap.enabled
67 if user.ldap_user? 67 if user.ldap_user?
68 # Check if LDAP user exists and match LDAP user_filter 68 # Check if LDAP user exists and match LDAP user_filter
69 - unless Gitlab::LDAP::Access.new.allowed?(user)  
70 - return false 69 + Gitlab::LDAP::Access.open do |adapter|
  70 + return false unless adapter.allowed?(user)
71 end 71 end
72 end 72 end
73 end 73 end
lib/gitlab/ldap/access.rb
@@ -14,7 +14,11 @@ module Gitlab @@ -14,7 +14,11 @@ module Gitlab
14 end 14 end
15 15
16 def allowed?(user) 16 def allowed?(user)
17 - !!Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) 17 + if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
  18 + !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter)
  19 + else
  20 + false
  21 + end
18 rescue 22 rescue
19 false 23 false
20 end 24 end
lib/gitlab/ldap/adapter.rb
@@ -64,7 +64,7 @@ module Gitlab @@ -64,7 +64,7 @@ module Gitlab
64 end 64 end
65 end 65 end
66 66
67 - entries = ldap.search(options).select do |entry| 67 + entries = ldap_search(options).select do |entry|
68 entry.respond_to? config.uid 68 entry.respond_to? config.uid
69 end 69 end
70 70
@@ -77,6 +77,26 @@ module Gitlab @@ -77,6 +77,26 @@ module Gitlab
77 users(*args).first 77 users(*args).first
78 end 78 end
79 79
  80 + def dn_matches_filter?(dn, filter)
  81 + ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any?
  82 + end
  83 +
  84 + def ldap_search(*args)
  85 + results = ldap.search(*args)
  86 +
  87 + if results.nil?
  88 + response = ldap.get_operation_result
  89 +
  90 + unless response.code.zero?
  91 + Rails.logger.warn("LDAP search error: #{response.message}")
  92 + end
  93 +
  94 + []
  95 + else
  96 + results
  97 + end
  98 + end
  99 +
80 private 100 private
81 101
82 def config 102 def config
lib/gitlab/ldap/person.rb
1 module Gitlab 1 module Gitlab
2 module LDAP 2 module LDAP
3 class Person 3 class Person
  4 + # Active Directory-specific LDAP filter that checks if bit 2 of the
  5 + # userAccountControl attribute is set.
  6 + # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
  7 + AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
  8 +
4 def self.find_by_uid(uid, adapter=nil) 9 def self.find_by_uid(uid, adapter=nil)
5 adapter ||= Gitlab::LDAP::Adapter.new 10 adapter ||= Gitlab::LDAP::Adapter.new
6 adapter.user(config.uid, uid) 11 adapter.user(config.uid, uid)
@@ -11,6 +16,11 @@ module Gitlab @@ -11,6 +16,11 @@ module Gitlab
11 adapter.user('dn', dn) 16 adapter.user('dn', dn)
12 end 17 end
13 18
  19 + def self.active_directory_disabled?(dn, adapter=nil)
  20 + adapter ||= Gitlab::LDAP::Adapter.new
  21 + adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
  22 + end
  23 +
14 def initialize(entry) 24 def initialize(entry)
15 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } 25 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
16 @entry = entry 26 @entry = entry
spec/lib/gitlab/ldap/ldap_access_spec.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::LDAP::Access do
  4 + let(:access) { Gitlab::LDAP::Access.new }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe :allowed? do
  8 + subject { access.allowed?(user) }
  9 +
  10 + context 'when the user cannot be found' do
  11 + before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
  12 +
  13 + it { should be_false }
  14 + end
  15 +
  16 + context 'when the user is found' do
  17 + before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) }
  18 +
  19 + context 'and the Active Directory disabled flag is set' do
  20 + before { Gitlab::LDAP::Person.stub(active_directory_disabled?: true) }
  21 +
  22 + it { should be_false }
  23 + end
  24 +
  25 + context 'and the Active Directory disabled flag is not set' do
  26 + before { Gitlab::LDAP::Person.stub(active_directory_disabled?: false) }
  27 +
  28 + it { should be_true }
  29 + end
  30 + end
  31 + end
  32 +end
spec/lib/gitlab/ldap/ldap_adapter_spec.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::LDAP::Adapter do
  4 + let(:adapter) { Gitlab::LDAP::Adapter.new }
  5 +
  6 + describe :dn_matches_filter? do
  7 + let(:ldap) { double(:ldap) }
  8 + subject { adapter.dn_matches_filter?(:dn, :filter) }
  9 + before { adapter.stub(ldap: ldap) }
  10 +
  11 + context "when the search is successful" do
  12 + context "and the result is non-empty" do
  13 + before { ldap.stub(search: [:foo]) }
  14 +
  15 + it { should be_true }
  16 + end
  17 +
  18 + context "and the result is empty" do
  19 + before { ldap.stub(search: []) }
  20 +
  21 + it { should be_false }
  22 + end
  23 + end
  24 +
  25 + context "when the search encounters an error" do
  26 + before { ldap.stub(search: nil, get_operation_result: double(code: 1, message: 'some error')) }
  27 +
  28 + it { should be_false }
  29 + end
  30 + end
  31 +end