Merge pull request #52 from github/ad-membership-valiator
Add ActiveDirectory membership validator
This commit is contained in:
Коммит
fd3d2fa200
|
@ -1,6 +1,7 @@
|
|||
require 'github/ldap/membership_validators/base'
|
||||
require 'github/ldap/membership_validators/classic'
|
||||
require 'github/ldap/membership_validators/recursive'
|
||||
require 'github/ldap/membership_validators/active_directory'
|
||||
|
||||
module GitHub
|
||||
class Ldap
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
module GitHub
|
||||
class Ldap
|
||||
module MembershipValidators
|
||||
ATTRS = %w(dn)
|
||||
OID = "1.2.840.113556.1.4.1941"
|
||||
|
||||
# Validates membership using the ActiveDirectory "in chain" matching rule.
|
||||
#
|
||||
# The 1.2.840.113556.1.4.1941 matching rule (LDAP_MATCHING_RULE_IN_CHAIN)
|
||||
# "walks the chain of ancestry in objects all the way to the root until
|
||||
# it finds a match".
|
||||
# Source: http://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx
|
||||
#
|
||||
# This means we have an efficient method of searching membership even in
|
||||
# nested groups, performed on the server side.
|
||||
class ActiveDirectory < Base
|
||||
def perform(entry)
|
||||
# short circuit validation if there are no groups to check against
|
||||
return true if groups.empty?
|
||||
|
||||
# search for the entry on the condition that the entry is a member
|
||||
# of one of the groups or their subgroups.
|
||||
#
|
||||
# Sets the entry to the base and scopes the search to the base,
|
||||
# according to the source documentation, found here:
|
||||
# http://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx
|
||||
matched = ldap.search \
|
||||
filter: membership_in_chain_filter(entry),
|
||||
base: entry.dn,
|
||||
scope: Net::LDAP::SearchScope_BaseObject,
|
||||
attributes: ATTRS
|
||||
|
||||
# membership validated if entry was matched and returned as a result
|
||||
matched.map(&:dn).include?(entry.dn)
|
||||
end
|
||||
|
||||
# Internal: Constructs a membership filter using the "in chain"
|
||||
# extended matching rule afforded by ActiveDirectory.
|
||||
#
|
||||
# Returns a Net::LDAP::Filter object.
|
||||
def membership_in_chain_filter(entry)
|
||||
group_dns.map do |dn|
|
||||
Net::LDAP::Filter.ex("memberOf:#{OID}", dn)
|
||||
end.reduce(:|)
|
||||
end
|
||||
|
||||
# Internal: the group DNs to check against.
|
||||
#
|
||||
# Returns an Array of String DNs.
|
||||
def group_dns
|
||||
@group_dns ||= groups.map(&:dn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ module GitHub
|
|||
# it consistently with the new approach.
|
||||
class Classic < Base
|
||||
def perform(entry)
|
||||
# short circuit validation if there are no groups to check against
|
||||
return true if groups.empty?
|
||||
|
||||
domains.each do |domain|
|
||||
|
|
|
@ -22,6 +22,9 @@ module GitHub
|
|||
ATTRS = %w(dn cn)
|
||||
|
||||
def perform(entry, depth = DEFAULT_MAX_DEPTH)
|
||||
# short circuit validation if there are no groups to check against
|
||||
return true if groups.empty?
|
||||
|
||||
domains.each do |domain|
|
||||
# find groups entry is an immediate member of
|
||||
membership = domain.search(filter: member_filter(entry), attributes: ATTRS)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
require_relative '../test_helper'
|
||||
|
||||
# NOTE: Since this strategy is targeted at ActiveDirectory and we don't have
|
||||
# AD setup in CI, we stub out actual queries and test against what AD *would*
|
||||
# respond with.
|
||||
|
||||
class GitHubLdapActiveDirectoryMembershipValidatorsTest < GitHub::Ldap::Test
|
||||
def setup
|
||||
@ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
|
||||
@domain = @ldap.domain("dc=github,dc=com")
|
||||
@entry = @domain.user?('user1')
|
||||
@validator = GitHub::Ldap::MembershipValidators::ActiveDirectory
|
||||
end
|
||||
|
||||
def make_validator(groups)
|
||||
groups = @domain.groups(groups)
|
||||
@validator.new(@ldap, groups)
|
||||
end
|
||||
|
||||
def test_validates_user_in_group
|
||||
@ldap.stub :search, [@entry] do
|
||||
validator = make_validator(%w(nested-group1))
|
||||
assert validator.perform(@entry)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_user_in_child_group
|
||||
@ldap.stub :search, [@entry] do
|
||||
validator = make_validator(%w(n-depth-nested-group1))
|
||||
assert validator.perform(@entry)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_user_in_grandchild_group
|
||||
@ldap.stub :search, [@entry] do
|
||||
validator = make_validator(%w(n-depth-nested-group2))
|
||||
assert validator.perform(@entry)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_user_in_great_grandchild_group
|
||||
@ldap.stub :search, [@entry] do
|
||||
validator = make_validator(%w(n-depth-nested-group3))
|
||||
assert validator.perform(@entry)
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_not_validate_user_not_in_group
|
||||
@ldap.stub :search, [] do
|
||||
validator = make_validator(%w(ghe-admins))
|
||||
refute validator.perform(@entry)
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_not_validate_user_not_in_any_group
|
||||
entry = @domain.user?('groupless-user1')
|
||||
|
||||
@ldap.stub :search, [] do
|
||||
validator = make_validator(%w(all-users))
|
||||
refute validator.perform(entry)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,6 +10,7 @@ FIXTURES = Pathname(File.expand_path('fixtures', __dir__))
|
|||
require 'github/ldap'
|
||||
require 'github/ldap/server'
|
||||
|
||||
require 'minitest/mock'
|
||||
require 'minitest/autorun'
|
||||
|
||||
if ENV.fetch('TESTENV', "apacheds") == "apacheds"
|
||||
|
|
Загрузка…
Ссылка в новой задаче