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 |