Commit 961810a4427ed452a9557b3f4bbe5fa8a1f21229
Exists in
spb-stable
and in
2 other branches
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.
Showing
6 changed files
with
101 additions
and
4 deletions
Show diff stats
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 |
@@ -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 |
@@ -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 |