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 66 if Gitlab.config.ldap.enabled
67 67 if user.ldap_user?
68 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 71 end
72 72 end
73 73 end
... ...
lib/gitlab/ldap/access.rb
... ... @@ -14,7 +14,11 @@ module Gitlab
14 14 end
15 15  
16 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 22 rescue
19 23 false
20 24 end
... ...
lib/gitlab/ldap/adapter.rb
... ... @@ -64,7 +64,7 @@ module Gitlab
64 64 end
65 65 end
66 66  
67   - entries = ldap.search(options).select do |entry|
  67 + entries = ldap_search(options).select do |entry|
68 68 entry.respond_to? config.uid
69 69 end
70 70  
... ... @@ -77,6 +77,26 @@ module Gitlab
77 77 users(*args).first
78 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 100 private
81 101  
82 102 def config
... ...
lib/gitlab/ldap/person.rb
1 1 module Gitlab
2 2 module LDAP
3 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 9 def self.find_by_uid(uid, adapter=nil)
5 10 adapter ||= Gitlab::LDAP::Adapter.new
6 11 adapter.user(config.uid, uid)
... ... @@ -11,6 +16,11 @@ module Gitlab
11 16 adapter.user('dn', dn)
12 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 24 def initialize(entry)
15 25 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
16 26 @entry = entry
... ...
spec/lib/gitlab/ldap/ldap_access_spec.rb 0 → 100644
... ... @@ -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 @@
  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
... ...