Skip to content

Merge dev-v2 branch into master #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 66 commits into from
Oct 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
ee14d5b
Add classic, recursive membership validators
mtodd Sep 24, 2014
72a543a
GitHub::Ldap is a class
mtodd Sep 24, 2014
c2dcbba
Fix doco for MembershipValidators module
mtodd Sep 24, 2014
f412a05
Missed Base
mtodd Sep 24, 2014
4017f92
Significant fixes and :nail_care:
mtodd Sep 24, 2014
4dbf604
Add rudimentary tests for MembershipValidators
mtodd Sep 24, 2014
00e8a03
Doco base methods
mtodd Sep 24, 2014
8d87584
CNs are hardcoded elsewhere
mtodd Sep 24, 2014
65dbcb9
Accept String DN for member_filter
mtodd Sep 24, 2014
97ba49b
Use member_filter for Recursive group search
mtodd Sep 24, 2014
90fb34a
Remove FIXME
mtodd Sep 24, 2014
a1d9d79
Extract attributes
mtodd Sep 24, 2014
7f8bf86
Fix subgroup fixture CNs
mtodd Sep 26, 2014
8bb1586
Add recursion tests for classic, recursive strategies
mtodd Sep 26, 2014
e97af8d
Autoload MembershipValidators::Base too
mtodd Sep 26, 2014
26d6bfd
Tweak docs
mtodd Sep 26, 2014
3efbdc1
Document attrs, make internal method private
mtodd Sep 26, 2014
0b98dc5
Refer to full class name in docs
mtodd Sep 26, 2014
76cefb8
Give these blocks some breathing room
mtodd Sep 26, 2014
d31baf5
Accept UID String for posix_member_filter
mtodd Sep 26, 2014
45bf499
Handle posixGroup memberships
mtodd Sep 26, 2014
5d7d6f4
Add deep nested posixGroup membership fixtures
mtodd Sep 26, 2014
084cdf2
Add tests for recursive posixGroup membership validation
mtodd Sep 26, 2014
58a5315
Hook up separate OpenLDAP install, build steps
mtodd Sep 27, 2014
7ae8fec
Merge pull request #45 from github/membership-validators
mtodd Sep 27, 2014
8759205
RVM exec rake for testing ApacheDS tests
mtodd Sep 27, 2014
392410f
Simplify a bit
mtodd Sep 27, 2014
5844fe6
Fast finish, simplify script, remove extraneous bits
mtodd Sep 29, 2014
2397389
Compute relative path to cd into, add debugging output
mtodd Sep 29, 2014
efcd72e
Bundle exec
mtodd Sep 29, 2014
f566aba
bundle install
mtodd Sep 29, 2014
4efafa6
Install, configure, seed OpenLDAP
mtodd Sep 30, 2014
6058337
:fire: unnecessary debugging
mtodd Sep 30, 2014
af39c0c
Fail on error, adjust base path computation
mtodd Sep 30, 2014
6e2d2c3
Wipe out old config first
mtodd Sep 30, 2014
e4f0a19
Switch things around to keep the path sane
mtodd Sep 30, 2014
2e4ce0f
Run the test suite with OpenLDAP
mtodd Sep 30, 2014
19d2fb9
Go back to dc=github,dc=com to match existing fixtures
mtodd Sep 30, 2014
2c6e174
Start test server with ladle when in apacheds test env only
mtodd Sep 30, 2014
33ad5a8
I hear commas are good
mtodd Sep 30, 2014
94d5b82
Create a 'common' fixture file
mtodd Oct 6, 2014
d2065f7
Hardcode server options
mtodd Oct 6, 2014
1e2b52e
Remove test-specific fixtures, use common seed
mtodd Oct 6, 2014
f0a3d75
Remove quiet option when verbose is set
mtodd Oct 6, 2014
19aa507
Use ENV.fetch with default
mtodd Oct 6, 2014
d80dbc5
Update one Domain test with correct fixture
mtodd Oct 6, 2014
010fbc4
Fix Domain posixGroup tests, fixtures
mtodd Oct 6, 2014
b3ac5f3
Add byebug as an development, test bundler dependency
mtodd Oct 6, 2014
1fe748e
Experiment with reusing the ApacheDS LDAP server for each test
mtodd Oct 6, 2014
34d63f6
byebug is supported on MRI 2.0, 2.1 only
mtodd Oct 6, 2014
84e37ce
Add supporting fixtures, fix email domain
mtodd Oct 6, 2014
919734e
Use common seed for OpenLDAP
mtodd Oct 6, 2014
46fea0d
Rewrite Domain tests
mtodd Oct 6, 2014
a1988ab
Include gidNumber in seed; fix posixGroup schema
mtodd Oct 6, 2014
102351c
Rewrite GitHub::Ldap tests
mtodd Oct 6, 2014
6502ab1
Fix Group tests, add fixture group with missing members
mtodd Oct 7, 2014
15953cc
Fix up posixGroup tests
mtodd Oct 8, 2014
db13ebd
Merge pull request #48 from github/cibuild-openldap
mtodd Oct 8, 2014
7014299
Update membership validator tests
mtodd Oct 8, 2014
108d290
Include general membership validator test cases
mtodd Oct 8, 2014
e14a910
Merge pull request #49 from github/fix-membership-validator-tests
mtodd Oct 8, 2014
655f231
add failing test
Oct 15, 2014
e132db4
pass through search options
Oct 15, 2014
3f02551
Merge pull request #51 from github/limit-attributes
jch Oct 15, 2014
5ec9b36
:fire: unneeded LDIF fixtures
mtodd Oct 15, 2014
9d154e8
Require membership validator strategies
mtodd Oct 15, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
language: ruby
rvm:
- 1.9.3
- 2.1.0
- 1.9.3
- 2.1.0

env:
- TESTENV=openldap
- TESTENV=apacheds

install:
- if [ "$TESTENV" = "openldap" ]; then ./script/install-openldap; fi
- bundle install

script:
- ./script/cibuild-$TESTENV

matrix:
fast_finish: true
notifications:
email: false
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in github-ldap.gemspec
gemspec

group :test, :development do
gem "byebug", :platforms => [:mri_20, :mri_21]
end
1 change: 1 addition & 0 deletions lib/github/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Ldap
require 'github/ldap/virtual_group'
require 'github/ldap/virtual_attributes'
require 'github/ldap/instrumentation'
require 'github/ldap/membership_validators'

include Instrumentation

Expand Down
8 changes: 6 additions & 2 deletions lib/github/ldap/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,15 @@ def valid_login?(login, password)
# Check if a user exists based in the `uid`.
#
# login: is the user's login
# search_options: Net::LDAP#search compatible options to pass through
#
# Returns the user if the login matches any `uid`.
# Returns nil if there are no matches.
def user?(login)
search(filter: login_filter(@uid, login), size: 1).first
def user?(login, search_options = {})
options = search_options.merge \
filter: login_filter(@uid, login),
size: 1
search(options).first
end

# Check if a user can be bound with a password.
Expand Down
22 changes: 15 additions & 7 deletions lib/github/ldap/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ def group_filter(group_names)

# Filter to check group membership.
#
# entry: finds groups this Net::LDAP::Entry is a member of (optional)
# entry: finds groups this entry is a member of (optional)
# Expects a Net::LDAP::Entry or String DN.
#
# Returns a Net::LDAP::Filter.
def member_filter(entry = nil)
if entry
entry = entry.dn if entry.respond_to?(:dn)
MEMBERSHIP_NAMES.
map {|n| Net::LDAP::Filter.eq(n, entry.dn) }.reduce(:|)
map {|n| Net::LDAP::Filter.eq(n, entry) }.reduce(:|)
else
MEMBERSHIP_NAMES.
map {|n| Net::LDAP::Filter.pres(n) }. reduce(:|)
map {|n| Net::LDAP::Filter.pres(n) }. reduce(:|)
end
end

Expand All @@ -41,10 +43,16 @@ def member_filter(entry = nil)
# uid_attr: specifies the memberUid attribute to match with
#
# Returns a Net::LDAP::Filter or nil if no entry has no UID set.
def posix_member_filter(entry, uid_attr)
if !entry[uid_attr].empty?
entry[uid_attr].map { |uid| Net::LDAP::Filter.eq("memberUid", uid) }.
reduce(:|)
def posix_member_filter(entry_or_uid, uid_attr = nil)
case entry_or_uid
when Net::LDAP::Entry
entry = entry_or_uid
if !entry[uid_attr].empty?
entry[uid_attr].map { |uid| Net::LDAP::Filter.eq("memberUid", uid) }.
reduce(:|)
end
when String
Net::LDAP::Filter.eq("memberUid", entry_or_uid)
end
end

Expand Down
17 changes: 17 additions & 0 deletions lib/github/ldap/membership_validators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'github/ldap/membership_validators/base'
require 'github/ldap/membership_validators/classic'
require 'github/ldap/membership_validators/recursive'

module GitHub
class Ldap
# Provides various strategies for validating membership.
#
# For example:
#
# groups = domain.groups(%w(Engineering))
# validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups)
# validator.perform(entry) #=> true
#
module MembershipValidators; end
end
end
37 changes: 37 additions & 0 deletions lib/github/ldap/membership_validators/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module GitHub
class Ldap
module MembershipValidators
class Base

# Internal: The GitHub::Ldap object to search domains with.
attr_reader :ldap

# Internal: an Array of Net::LDAP::Entry group objects to validate with.
attr_reader :groups

# Public: Instantiate new validator.
#
# - ldap: GitHub::Ldap object
# - groups: Array of Net::LDAP::Entry group objects
def initialize(ldap, groups)
@ldap = ldap
@groups = groups
end

# Abstract: Performs the membership validation check.
#
# Returns Boolean whether the entry's membership is validated or not.
# def perform(entry)
# end

# Internal: Domains to search through.
#
# Returns an Array of GitHub::Ldap::Domain objects.
def domains
@domains ||= ldap.search_domains.map { |base| ldap.domain(base) }
end
private :domains
end
end
end
end
33 changes: 33 additions & 0 deletions lib/github/ldap/membership_validators/classic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module GitHub
class Ldap
module MembershipValidators
# Validates membership using `GitHub::Ldap::Domain#membership`.
#
# This is a simple wrapper for existing functionality in order to expose
# it consistently with the new approach.
class Classic < Base
def perform(entry)
return true if groups.empty?

domains.each do |domain|
membership = domain.membership(entry, group_names)

if !membership.empty?
entry[:groups] = membership
return true
end
end

false
end

# Internal: the group names to look up membership for.
#
# Returns an Array of String group names (CNs).
def group_names
@group_names ||= groups.map { |g| g[:cn].first }
end
end
end
end
end
90 changes: 90 additions & 0 deletions lib/github/ldap/membership_validators/recursive.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
module GitHub
class Ldap
module MembershipValidators
# Validates membership recursively.
#
# The first step checks whether the entry is a direct member of the given
# groups. If they are, then we've validated membership successfully.
#
# If not, query for all of the groups that have our groups as members,
# then we check if the entry is a member of any of those.
#
# This is repeated until the entry is found, recursing and requesting
# groups in bulk each iteration until we hit the maximum depth allowed
# and have to give up.
#
# This results in a maximum of `depth` queries (per domain) to validate
# membership in a list of groups.
class Recursive < Base
include Filter

DEFAULT_MAX_DEPTH = 9
ATTRS = %w(dn cn)

def perform(entry, depth = DEFAULT_MAX_DEPTH)
domains.each do |domain|
# find groups entry is an immediate member of
membership = domain.search(filter: member_filter(entry), attributes: ATTRS)

# success if any of these groups match the restricted auth groups
return true if membership.any? { |entry| group_dns.include?(entry.dn) }

# give up if the entry has no memberships to recurse
next if membership.empty?

# recurse to at most `depth`
depth.times do |n|
# find groups whose members include membership groups
membership = domain.search(filter: membership_filter(membership), attributes: ATTRS)

# success if any of these groups match the restricted auth groups
return true if membership.any? { |entry| group_dns.include?(entry.dn) }

# give up if there are no more membersips to recurse
break if membership.empty?
end

# give up on this base if there are no memberships to test
next if membership.empty?
end

false
end

# Internal: Construct a filter to find groups this entry is a direct
# member of.
#
# Overloads the included `GitHub::Ldap::Filters#member_filter` method
# to inject `posixGroup` handling.
#
# Returns a Net::LDAP::Filter object.
def member_filter(entry_or_uid, uid = ldap.uid)
filter = super(entry_or_uid)

if ldap.posix_support_enabled?
if posix_filter = posix_member_filter(entry_or_uid, uid)
filter |= posix_filter
end
end

filter
end

# Internal: Construct a filter to find groups whose members are the
# Array of String group DNs passed in.
#
# Returns a String filter.
def membership_filter(groups)
groups.map { |entry| member_filter(entry, :cn) }.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
2 changes: 2 additions & 0 deletions lib/github/ldap/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def self.start_server(options = {})
@server_options[:domain] = @server_options[:user_domain]
@server_options[:tmpdir] ||= server_tmp

@server_options[:quiet] = false if @server_options[:verbose]

@ldap_server = Ladle::Server.new(@server_options)
@ldap_server.start
end
Expand Down
7 changes: 7 additions & 0 deletions script/cibuild-apacheds
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env sh
set -e
set -x

cd `dirname $0`/..

bundle exec rake
7 changes: 7 additions & 0 deletions script/cibuild-openldap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env sh
set -e
set -x

cd `dirname $0`/..

bundle exec rake
44 changes: 44 additions & 0 deletions script/install-openldap
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env sh
set -e
set -x

BASE_PATH="$( cd `dirname $0`/../test/fixtures/openldap && pwd )"
SEED_PATH="$( cd `dirname $0`/../test/fixtures/common && pwd )"

DEBIAN_FRONTEND=noninteractive sudo -E apt-get install -y --force-yes slapd time ldap-utils

sudo /etc/init.d/slapd stop

TMPDIR=$(mktemp -d)
cd $TMPDIR

# Delete data and reconfigure.
sudo cp -v /var/lib/ldap/DB_CONFIG ./DB_CONFIG
sudo rm -rf /etc/ldap/slapd.d/*
sudo rm -rf /var/lib/ldap/*
sudo cp -v ./DB_CONFIG /var/lib/ldap/DB_CONFIG
sudo slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/slapd.conf.ldif
# Load memberof and ref-int overlays and configure them.
sudo slapadd -F /etc/ldap/slapd.d -b "cn=config" -l $BASE_PATH/memberof.ldif

# Add base domain.
sudo slapadd -F /etc/ldap/slapd.d <<EOM
dn: dc=github,dc=com
objectClass: top
objectClass: domain
dc: github
EOM

sudo chown -R openldap.openldap /etc/ldap/slapd.d
sudo chown -R openldap.openldap /var/lib/ldap

sudo /etc/init.d/slapd start

# Import seed data.
# NOTE: use ldapadd in order for memberOf and refint to apply, instead of:
# /vagrant/services/ldap/openldap/seed.rb | sudo slapadd -F /etc/ldap/slapd.d
cat $SEED_PATH/seed.ldif |
/usr/bin/time sudo ldapadd -x -D "cn=admin,dc=github,dc=com" -w passworD1 \
-h localhost -p 389

sudo rm -rf $TMPDIR
Loading