-
Notifications
You must be signed in to change notification settings - Fork 26
Support referral chasing for Active Directory member validation #95
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
Changes from all commits
Commits
Show all changes
100 commits
Select commit
Hold shift + click to select a range
ecf5c43
Load a global catalog connection & add interface for searching it
51e72c0
Use global catalog to detect user, if server is Active Directory
203063d
Add tests for auth & unauth default Global Catalog settings
a23ccef
Test format of Global Catalog search results
c03c0de
Split up auth'd, unauth'd and global default settings tests
d4ae1ab
Updated documentation
8244f7c
Only initialize global catalog if server is Active Directory
3cf1c5a
Make capabilities public so domain can decide whether it's Active Dir
baef44d
Test for default instrumentation service on Global Catalog
e05f40c
Added doc for global_catalog_search
6125bc5
Test for domain to use global catalog if it's Active Directory
a34264b
Test for using default search on non-Active Directory servers
a00750f
Keep reference to credentials & instrumentation service for Global Ca…
6787585
Updates to Domain#user? & use of Global Catalog
c38ffca
Test that global catalog search uses empty base DN
ab2d49a
Set auth method for Global Catalog explicitly to :simple
3af0a88
Make sure global catalog search returns first entry from result array
dd24ee7
Document reason for auth references; remove redundant ivar
8d9eca3
Update doc
76db68a
Added test group to Gemfile
d69b0ba
Use assert_nil
9e9f9e9
Drop message in test
ad67b78
Created new ActiveDirectory user search class; moved tests
73ddf22
Added default strategy class for UserSearch
3d73e87
Load the default user search class; use strategy for Domain#user?
3f1838c
Override search for AD user search
115abb7
Update tests with new method signature for UserSearch#perform
f550ce5
Configure user search strategy
32840bc
Removed dead code -- was moved to its own class
89eca1e
More tests for user search strategy
faf1a16
Reverse the merge order for default search
2538aa0
Updated domain tests since adding user search strategy
d759075
Removed unnecessary tests
1ab77c0
Don't need this test
2e33e8b
Clean up requires
cf59b4c
Created a GlobalCatalog object; expose :connection on LDAP
0b22add
Configure user search consistent with other config strategies
5f9e3d7
Make active_directory_capability? private again
0ea4a93
Use mock utility for mock objects; Don't stub :new on Net::LDAP
18eb399
Test auth on user search strategy; minor test cleanup
32222d5
Updated documentation
5012000
Explictly override options for setting the base DN to ""
a522641
better hash structure
a3163fe
Updated documentation
c3ac0b3
Don't fall back on default user search for non-AD controllers
4154214
Make search & options private on ActiveDirectory user search
3da33e4
Make global connection interface private
735a42b
update documentation
3bc3979
Remove unneded test & condition
c05c4d7
First draft of referral chasing: set up referral connection properties
9afe164
WIP: Added referral chasing method, will move to GitHub::Ldap
1fce717
Moved chase_referral to ldap class
526d63a
Cache new referral connections as we go
82704e5
removed old chase_referral method
022d455
Add method to reset base dn on search filter for use with referrals
652633b
Reset base dn on filter before searcing on referral controller
bb149ca
cleanup
218675b
Use GitHub::Ldap instead of Net::LDAP for referral connections
971698c
Don't reset base_dn, will pass in FQDN for groups from client
990cca8
No need for DN matcher
54defdb
Create a ConnectionPool object to encapsulate caching connection objects
4b0ccff
Split connection pool tests
edd8107
A little cleanup
5d7c294
Abstracted referral chasing into its own class
96ba488
load referral_chaser; exposed admin user & pw for referrals to use
9edc1c5
Removed dead code
7f3e6f2
Use new referral chaser class in ActiveDirectory validator
b7da6e7
Pushing referral aggregation from callsite into ReferallChaser
704ea39
Use base connection's port as the default port
8199555
Use ReferralChaser to do all the search heavy lifting for AD
d3dc5c4
Updated documentation
644fb68
Updated tests
e759522
Added a GitHub::Ldap::URL object to encapsulate parsing ldap urls
5cefd97
Remove redundant test
0ac330a
Only iterate over Referall type entries in ReferralChaser
1a95540
memoize the referral chaser
e2c3675
check entry for nil when collecting referrals
a4573d7
Remove test file; clean up merge
3eddd8b
A bit more merge cleanup
7cdbe86
Minor style/cleanup
80a6127
Don't need result reference
f052559
Better format for private method
77b4209
Add host interface to ldap; updated tests
518bd85
Moved connection cache to its own class
27cf716
move mocha requirement to test_helper
ef20459
Better use of mocks
51f793c
Fix mock for referral_chaser_test
a0f1f24
Fixing CI: don't use a real connection
88319aa
CI Fixes: move all setup code inline
cc3fd3a
Removing ruby 1.9.3 from CI
85ab9f8
Use correct port for LDAP connection
ed66f8d
CI Fixes: use the right credentials to connect to test ldap server
6ad401a
CI Fixes: use correct connection attrs for tests
142e3fd
Test Ruby 2.0
mtodd 64c6e08
Configure connection with GitHub::Ldap::Test#options
mtodd 2b80058
Rename mock_connection to ldap
mtodd e7de8e7
Setup test hosts for connection caching
mtodd 02cef0b
Document user search strategy callsite
15990be
Test for invalid URL strings as well as bad schemes in ReferralChaser
60e7b7f
Fixing some merge-fu: use @ldap not @mock_connection
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| module GitHub | ||
| class Ldap | ||
|
|
||
| # A simple cache of GitHub::Ldap objects to prevent creating multiple | ||
| # instances of connections that point to the same URI/host. | ||
| class ConnectionCache | ||
|
|
||
| # Public - Create or return cached instance of GitHub::Ldap created with options, | ||
| # where the cache key is the value of options[:host]. | ||
| # | ||
| # options - Initialization attributes suitable for creating a new connection with | ||
| # GitHub::Ldap.new(options) | ||
| # | ||
| # Returns true or false. | ||
| def self.get_connection(options={}) | ||
| @cache ||= self.new | ||
| @cache.get_connection(options) | ||
| end | ||
|
|
||
| def get_connection(options) | ||
| @connections ||= {} | ||
| @connections[options[:host]] ||= GitHub::Ldap.new(options) | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| module GitHub | ||
| class Ldap | ||
|
|
||
| # This class adds referral chasing capability to a GitHub::Ldap connection. | ||
| # | ||
| # See: https://technet.microsoft.com/en-us/library/cc978014.aspx | ||
| # http://www.umich.edu/~dirsvcs/ldap/doc/other/ldap-ref.html | ||
| # | ||
| class ReferralChaser | ||
|
|
||
| # Public - Creates a ReferralChaser that decorates an instance of GitHub::Ldap | ||
| # with additional functionality to the #search method, allowing it to chase | ||
| # any referral entries and aggregate the results into a single response. | ||
| # | ||
| # connection - The instance of GitHub::Ldap to use for searching. Will use | ||
| # the connection's authentication, (admin_user and admin_password) as credentials | ||
| # for connecting to referred domain controllers. | ||
| def initialize(connection) | ||
| @connection = connection | ||
| @admin_user = connection.admin_user | ||
| @admin_password = connection.admin_password | ||
| @port = connection.port | ||
| end | ||
|
|
||
| # Public - Search the domain controller represented by this instance's connection. | ||
| # If a referral is returned, search only one of the domain controllers indicated | ||
| # by the referral entries, per RFC 4511 (https://tools.ietf.org/html/rfc4511): | ||
| # | ||
| # "If the client wishes to progress the operation, it contacts one of | ||
| # the supported services found in the referral. If multiple URIs are | ||
| # present, the client assumes that any supported URI may be used to | ||
| # progress the operation." | ||
| # | ||
| # options - is a hash with the same options that Net::LDAP::Connection#search supports. | ||
| # Referral searches will use the given options, but will replace options[:base] | ||
| # with the referral URL's base search dn. | ||
| # | ||
| # Does not take a block argument as GitHub::Ldap and Net::LDAP::Connection#search do. | ||
| # | ||
| # Will not recursively follow any subsequent referrals. | ||
| # | ||
| # Returns an Array of Net::LDAP::Entry. | ||
| def search(options) | ||
| search_results = [] | ||
| referral_entries = [] | ||
|
|
||
| search_results = connection.search(options) do |entry| | ||
| if entry && entry[:search_referrals] | ||
| referral_entries << entry | ||
| end | ||
| end | ||
|
|
||
| unless referral_entries.empty? | ||
| entry = referral_entries.first | ||
| referral_string = entry[:search_referrals].first | ||
| if GitHub::Ldap::URL.valid?(referral_string) | ||
| referral = Referral.new(referral_string, admin_user, admin_password, port) | ||
| search_results = referral.search(options) | ||
| end | ||
| end | ||
|
|
||
| Array(search_results) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| attr_reader :connection, :admin_user, :admin_password, :port | ||
|
|
||
| # Represents a referral entry from an LDAP search result. Constructs a corresponding | ||
| # GitHub::Ldap object from the paramaters on the referral_url and provides a #search | ||
| # method to continue the search on the referred domain. | ||
| class Referral | ||
| def initialize(referral_url, admin_user, admin_password, port=nil) | ||
| url = GitHub::Ldap::URL.new(referral_url) | ||
| @search_base = url.dn | ||
|
|
||
| connection_options = { | ||
| host: url.host, | ||
| port: port || url.port, | ||
| scope: url.scope, | ||
| admin_user: admin_user, | ||
| admin_password: admin_password | ||
| } | ||
|
|
||
| @connection = GitHub::Ldap::ConnectionCache.get_connection(connection_options) | ||
| end | ||
|
|
||
| # Search the referred domain controller with options, merging in the referred search | ||
| # base DN onto options[:base]. | ||
| def search(options) | ||
| connection.search(options.merge(base: search_base)) | ||
| end | ||
|
|
||
| attr_reader :search_base, :connection | ||
| end | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| module GitHub | ||
| class Ldap | ||
|
|
||
| # This class represents an LDAP URL | ||
| # | ||
| # See: https://tools.ietf.org/html/rfc4516#section-2 | ||
| # https://docs.oracle.com/cd/E19957-01/817-6707/urls.html | ||
| # | ||
| class URL | ||
| extend Forwardable | ||
| SCOPES = { | ||
| "base" => Net::LDAP::SearchScope_BaseObject, | ||
| "one" => Net::LDAP::SearchScope_SingleLevel, | ||
| "sub" => Net::LDAP::SearchScope_WholeSubtree | ||
| } | ||
| SCOPES.default = Net::LDAP::SearchScope_BaseObject | ||
|
|
||
| attr_reader :dn, :attributes, :scope, :filter | ||
|
|
||
| def_delegators :@uri, :port, :host, :scheme | ||
|
|
||
| # Public - Creates a new GitHub::Ldap::URL object with :port, :host and :scheme | ||
| # delegated to a URI object parsed from url_string, and then parses the | ||
| # query params according to the LDAP specification. | ||
| # | ||
| # url_string - An LDAP URL string. | ||
| # returns - a GitHub::Ldap::URL with the following attributes: | ||
| # host - Name or IP of the LDAP server. | ||
| # port - The given port, defaults to 389. | ||
| # dn - The base search DN. | ||
| # attributes - The comma-delimited list of attributes to be returned. | ||
| # scope - The scope of the search. | ||
| # filter - Search filter to apply to entries within the specified scope of the search. | ||
| # | ||
| # Supported LDAP URL strings look like this, where sections in brackets are optional: | ||
| # | ||
| # ldap[s]://[hostport][/[dn[?[attributes][?[scope][?[filter]]]]]] | ||
| # | ||
| # where: | ||
| # | ||
| # hostport is a host name with an optional ":portnumber" | ||
| # dn is the base DN to be used for an LDAP search operation | ||
| # attributes is a comma separated list of attributes to be retrieved | ||
| # scope is one of these three strings: base one sub (default=base) | ||
| # filter is LDAP search filter as used in a call to ldap_search | ||
| # | ||
| # For example: | ||
| # | ||
| # ldap://dc4.ghe.local:456/CN=Maggie,DC=dc4,DC=ghe,DC=local?cn,mail?base?(cn=Charlie) | ||
| # | ||
| def initialize(url_string) | ||
| if !self.class.valid?(url_string) | ||
| raise InvalidLdapURLException.new("Invalid LDAP URL: #{url_string}") | ||
| end | ||
| @uri = URI(url_string) | ||
| @dn = URI.unescape(@uri.path.sub(/^\//, "")) | ||
| if @uri.query | ||
| @attributes, @scope, @filter = @uri.query.split("?") | ||
| end | ||
| end | ||
|
|
||
| def self.valid?(url_string) | ||
| url_string =~ URI::regexp && ["ldap", "ldaps"].include?(URI(url_string).scheme) | ||
| end | ||
|
|
||
| # Maps the returned scope value from the URL to one of Net::LDAP::Scopes | ||
| # | ||
| # The URL scope value can be one of: | ||
| # "base" - retrieves information only about the DN (base_dn) specified. | ||
| # "one" - retrieves information about entries one level below the DN (base_dn) specified. The base entry is not included in this scope. | ||
| # "sub" - retrieves information about entries at all levels below the DN (base_dn) specified. The base entry is included in this scope. | ||
| # | ||
| # Which will map to one of the following Net::LDAP::Scopes: | ||
| # SearchScope_BaseObject = 0 | ||
| # SearchScope_SingleLevel = 1 | ||
| # SearchScope_WholeSubtree = 2 | ||
| # | ||
| # If no scope or an invalid scope is given, defaults to SearchScope_BaseObject | ||
| def net_ldap_scope | ||
| Net::LDAP::SearchScopes[SCOPES[scope]] | ||
| end | ||
|
|
||
| class InvalidLdapURLException < Exception; end | ||
| end | ||
| end | ||
| end | ||
|
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the
portspecified in the referral URL be preferred?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
sowe should prefer the user config. Ruby'sURI(which is what's underlyingGitHub::Ldap::URL) gives a default port of 389 because it's smart about the protocol,ldap. I've yet to see a referral come back with a port on the URL, so I think in this case we do want to prefer the port designated by the user, which would not be 389 and would instead by 686 if they're using TLS/LDAPS.That said, I just tested
URIin pry, and it's also smart about using 686 if the protocol isldaps. If I knew that the referral URLs were consistent about the protocol I'd say let's prefer the port returned by the referral. I'll set up some test conditions on my local AD forest & find out.