Merge pull request #52 from github/ad-membership-valiator

Add ActiveDirectory membership validator
This commit is contained in:
Matt Todd 2014-10-17 10:54:38 -07:00
Родитель 0bdbdf2c0d 1c37c2291a
Коммит fd3d2fa200
6 изменённых файлов: 125 добавлений и 0 удалений

Просмотреть файл

@ -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"