github-ldap 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in github-ldap.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 David Calavera
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Github::Ldap
2
+
3
+ GitHub-Ldap is a wrapper on top of Net::LDAP to make it human friendly.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'github-ldap'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install github-ldap
18
+
19
+ ## Usage
20
+
21
+ GitHub-Ldap let you use an external ldap server to authenticate your users with.
22
+
23
+ There are a few configuration options required to use this adapter:
24
+
25
+ * host: is the host address where the ldap server lives.
26
+ * port: is the port where the ldap server lives.
27
+ * admin_user: is the the ldap administrator user. Required to perform search operation.
28
+ * admin_password: is the password for the administrator user. Simple authentication is required on the server.
29
+ * encryptation: is the encryptation protocol, disabled by default. The valid options are `ssl` and `tls`.
30
+ * user_domain: is the default ldap domain base.
31
+ * uid: is the field name in the ldap server used to authenticate your users, in ActiveDirectory this is `sAMAccountName`.
32
+
33
+ Initialize a new adapter using those required options:
34
+
35
+ ```ruby
36
+ ldap = GitHub::Ldap.new options
37
+ ```
38
+
39
+ There is also an optional configuration setting that you can add:
40
+
41
+ * user_groups: is an array of groups used to restrict access to users only in those groups.
42
+
43
+ ## Testing
44
+
45
+ GitHub-Ldap uses [ladle](https://github.com/NUBIC/ladle) for testing. Ladle is not required by default, so you'll need to add it to your gemfile separatedly and require it.
46
+
47
+ Once you have it installed you can start the testing ldap server in the setup phase for your tests:
48
+
49
+ ```ruby
50
+ require 'github/ldap/server'
51
+
52
+ def setup
53
+ GitHub::Ldap.start_server
54
+ end
55
+
56
+ def teardown
57
+ GitHub::Ldap.stop_server
58
+ end
59
+ ```
60
+
61
+ GitHub-Ldap includes a set of configured users for testing, but you can provide your own users into a ldif file:
62
+
63
+ ```ruby
64
+ def setup
65
+ GitHub::Ldap.start_server \
66
+ users_fixtures: ldif_path
67
+ end
68
+ ```
69
+
70
+ If you provide your own user fixtures, you'll probably need to change the default user domain, the administrator name and her password:
71
+
72
+ ```ruby
73
+ def setup
74
+ GitHub::Ldap.start_server \
75
+ user_fixtures: ldif_path,
76
+ user_domain: 'dc=evilcorp,dc=com'
77
+ admin_user: 'uid=eviladmin,dc=evilcorp,dc=com',
78
+ admin_password: 'correct horse battery staple'
79
+ end
80
+ ```
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork it
85
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
86
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
87
+ 4. Push to the branch (`git push origin my-new-feature`)
88
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "github-ldap"
5
+ spec.version = "1.0.0"
6
+ spec.authors = ["David Calavera"]
7
+ spec.email = ["david.calavera@gmail.com"]
8
+ spec.description = %q{Ldap authentication for humans}
9
+ spec.summary = %q{Ldap client authentication wrapper without all the boilerplate}
10
+ spec.homepage = "https://github.com/github/github-ldap"
11
+ spec.license = "MIT"
12
+
13
+ spec.files = `git ls-files`.split($/)
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.require_paths = ["lib"]
17
+
18
+ spec.add_dependency 'net-ldap', '>= 0.2.2'
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency 'ladle'
22
+ spec.add_development_dependency 'minitest', '~> 5'
23
+ spec.add_development_dependency "rake"
24
+ end
@@ -0,0 +1,111 @@
1
+ module GitHub
2
+ class Ldap
3
+ require 'net/ldap'
4
+
5
+ def initialize(options = {})
6
+ @user_domain = options[:user_domain]
7
+ @user_groups = Array(options[:user_groups])
8
+ @uid = options[:uid] || "sAMAccountName"
9
+
10
+ @ldap = Net::LDAP.new({
11
+ host: options[:host],
12
+ port: options[:port]
13
+ })
14
+
15
+ @ldap.authenticate(options[:admin_user], options[:admin_password])
16
+
17
+ if encryptation = check_encryptation(options[:encryptation])
18
+ @ldap.encryptation(encryptation)
19
+ end
20
+ end
21
+
22
+ # Generate a filter to get the configured groups in the ldap server.
23
+ # Takes the list of the group names and generate a filter for the groups
24
+ # with cn that match and also include members:
25
+ #
26
+ # Returns the ldap filter.
27
+ def group_filter
28
+ or_filters = @user_groups.map {|g| Net::LDAP::Filter.eq("cn", g)}.reduce(:|)
29
+ Net::LDAP::Filter.pres("member") & or_filters
30
+ end
31
+
32
+ # List the groups in the ldap server that match the configured ones.
33
+ #
34
+ # Returns a list of ldap entries for the configured groups.
35
+ def groups
36
+ @ldap.search(base: @user_domain,
37
+ attributes: %w{ou cn dn sAMAccountName member},
38
+ filter: group_filter)
39
+ end
40
+
41
+ # Check if the user is include in any of the configured groups.
42
+ #
43
+ # user_dn: is the dn for the user ldap entry.
44
+ #
45
+ # Returns true if the user belongs to any of the groups.
46
+ # Returns false otherwise.
47
+ def groups_contain_user?(user_dn)
48
+ return true if @user_groups.empty?
49
+
50
+ members = groups.map(&:member).reduce(:+).uniq
51
+ members.include?(user_dn)
52
+ end
53
+
54
+ # Check if the user credentials are valid.
55
+ #
56
+ # login: is the user's login.
57
+ # password: is the user's password.
58
+ #
59
+ # Returns a Ldap::Entry if the credentials are valid.
60
+ # Returns nil if the credentials are invalid.
61
+ def valid_login?(login, password)
62
+ result = @ldap.bind_as(
63
+ base: @user_domain,
64
+ limit: 1,
65
+ filter: Net::LDAP::Filter.eq(@uid, login),
66
+ password: password)
67
+
68
+ return result.first if result.is_a?(Array)
69
+ end
70
+
71
+ # Authenticate a user with the ldap server.
72
+ #
73
+ # login: is the user's login. This method doesn't accept email identifications.
74
+ # password: is the user's password.
75
+ #
76
+ # Returns the user info if the credentials are valid and there are no groups configured.
77
+ # Returns the user info if the credentials are valid and the user belongs to a configured group.
78
+ # Returns nil if the credentials are invalid
79
+ def authenticate!(login, password)
80
+ user = valid_login?(login, password)
81
+ return user if user && groups_contain_user?(user.dn)
82
+ end
83
+
84
+ # Check the legacy auth configuration options (before David's war with omniauth)
85
+ # to determine whether to use encryptation or not.
86
+ #
87
+ # encryptation: is the encryptation method, either 'ssl', 'tls', 'simple_tls' or 'start_tls'.
88
+ #
89
+ # Returns the real encryptation type.
90
+ def check_encryptation(encryptation)
91
+ return unless encryptation
92
+
93
+ case auth_method.downcase.to_sym
94
+ when :ssl, :simple_tls
95
+ :simple_tls
96
+ when :tls, :start_tls
97
+ :start_tls
98
+ end
99
+ end
100
+
101
+ # Utility method to check if the connection with the server can be stablished.
102
+ # It tries to bind with the ldap auth default configuration.
103
+ #
104
+ # Return true if the connection is successful.
105
+ # Return false if the authentication settings are not valid.
106
+ # Raises an Net::LDAP::LdapError if the connection fails.
107
+ def test_connection
108
+ @ldap.bind
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,70 @@
1
+ version: 1
2
+
3
+ # Organizations
4
+ dn: dc=github,dc=com
5
+ objectClass: dcObject
6
+ objectClass: organization
7
+ dc: github
8
+ o: GitHub Inc.
9
+
10
+ dn: ou=Group,dc=github,dc=com
11
+ objectclass: organizationalUnit
12
+ ou: Group
13
+
14
+ # Groups
15
+ dn: cn=People,ou=Group,dc=github,dc=com
16
+ cn: People
17
+ objectClass: groupOfNames
18
+ member: uid=sean,dc=github,dc=com
19
+
20
+ dn: cn=Enterprise,ou=Group,dc=github,dc=com
21
+ cn: Enterprise
22
+ objectClass: groupOfNames
23
+ member: uid=calavera,dc=github,dc=com
24
+
25
+ # Users
26
+ dn: uid=admin,dc=github,dc=com
27
+ objectClass: top
28
+ objectClass: person
29
+ objectClass: organizationalPerson
30
+ objectClass: inetOrgPerson
31
+ cn: system administrator
32
+ sn: administrator
33
+ displayName: Directory Superuser
34
+ uid: admin
35
+ userPassword: secret
36
+
37
+ dn: uid=sean,dc=github,dc=com
38
+ cn: Sean Bryant
39
+ cn: Sean
40
+ sn: Bryant
41
+ uid: sean
42
+ userPassword: secret
43
+ mail: sean@github.com
44
+ mail: sbryant@github.com
45
+ objectClass: inetOrgPerson
46
+
47
+ dn: uid=calavera,dc=github,dc=com
48
+ cn: David Calavera
49
+ cn: David
50
+ sn: Calavera
51
+ uid: calavera
52
+ userPassword: secret
53
+ mail: calavera@github.com
54
+ objectClass: inetOrgPerson
55
+
56
+ dn: uid=ldaptest,dc=github,dc=com
57
+ cn: LDAP
58
+ sn: Test
59
+ uid: ldaptest
60
+ userPassword: secret
61
+ mail: ldaptest@github.com
62
+ objectClass: inetOrgPerson
63
+
64
+ dn: uid=newuserindb,dc=github,dc=com
65
+ cn: LDAP
66
+ sn: Test
67
+ uid: newuserindb
68
+ userPassword: secret
69
+ mail: newuserindb@github.com
70
+ objectClass: inetOrgPerson
@@ -0,0 +1,52 @@
1
+ module GitHub
2
+ class Ldap
3
+ require 'ladle'
4
+
5
+ # Preconfigured user fixtures. If you want to use them for your own tests.
6
+ DEFAULT_FIXTURES_PATH = File.expand_path('fixtures.ldif', File.dirname(__FILE__))
7
+
8
+ DEFAULT_SERVER_OPTIONS = {
9
+ user_fixtures: DEFAULT_FIXTURES_PATH,
10
+ user_domain: 'dc=github,dc=com',
11
+ admin_user: 'uid=admin,dc=github,dc=com',
12
+ admin_password: 'secret',
13
+ quiet: true,
14
+ port: 3897
15
+ }
16
+
17
+ class << self
18
+
19
+ # server_options: is the options used to start the server,
20
+ # useful to know in development.
21
+ attr_reader :server_options
22
+
23
+ # ldap_server: is the instance of the testing ldap server,
24
+ # you should never interact with it,
25
+ # but it's used to grecefully stop it after your tests finalize.
26
+ attr_reader :ldap_server
27
+ end
28
+
29
+ # Start a testing server.
30
+ # If there is already a server initialized it doesn't do anything.
31
+ #
32
+ # options: is a hash with the custom options for the server.
33
+ def self.start_server(options = {})
34
+ @server_options = DEFAULT_SERVER_OPTIONS.merge(options)
35
+
36
+ @ldap_server ||= Ladle::Server.new(
37
+ allow_anonymous: false,
38
+ ldif: server_options[:user_fixtures],
39
+ domain: server_options[:user_domain],
40
+ port: server_options[:port],
41
+ quiet: server_options[:quiet])
42
+
43
+ @ldap_server.start
44
+ end
45
+
46
+ # Stop the testing server.
47
+ # If there is no server started this method doesn't do anything.
48
+ def self.stop_server
49
+ ldap_server && ldap_server.stop
50
+ end
51
+ end
52
+ end
data/test/ldap_test.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ class GitHubLdapTest < Minitest::Test
4
+ def setup
5
+ GitHub::Ldap.start_server
6
+
7
+ @options = GitHub::Ldap.server_options.merge \
8
+ host: 'localhost',
9
+ uid: 'uid'
10
+
11
+ @ldap = GitHub::Ldap.new(@options)
12
+ end
13
+
14
+ def teardown
15
+ GitHub::Ldap.stop_server
16
+ end
17
+
18
+ def test_connection_with_default_options
19
+ assert @ldap.test_connection, "Ldap connection expected to succeed"
20
+ end
21
+
22
+ def test_user_valid_login
23
+ user = @ldap.valid_login?('calavera', 'secret')
24
+ assert_equal 'uid=calavera,dc=github,dc=com', user.dn
25
+ end
26
+
27
+ def test_user_with_invalid_password
28
+ assert !@ldap.valid_login?('calavera', 'foo'),
29
+ "Login `calavera` expected to be invalid with password `foo`"
30
+ end
31
+
32
+ def test_user_with_invalid_login
33
+ assert !@ldap.valid_login?('bar', 'foo'),
34
+ "Login `bar` expected to be invalid with password `foo`"
35
+ end
36
+
37
+ def test_groups_in_server
38
+ options = @options.merge(:user_groups => %w(Enterprise People))
39
+ assert_equal 2, GitHub::Ldap.new(options).groups.size
40
+ end
41
+
42
+ def test_user_in_group
43
+ options = @options.merge(:user_groups => %w(Enterprise People))
44
+ ldap = GitHub::Ldap.new(options)
45
+ user = ldap.valid_login?('calavera', 'secret')
46
+
47
+ assert ldap.groups_contain_user?(user.dn),
48
+ "Expected `Enterprise` or `Poeple` to include the member `#{user.dn}`"
49
+ end
50
+
51
+ def test_user_not_in_different_group
52
+ options = @options.merge(:user_groups => %w(People))
53
+ ldap = GitHub::Ldap.new(options)
54
+ user = ldap.valid_login?('calavera', 'secret')
55
+
56
+ assert !ldap.groups_contain_user?(user.dn),
57
+ "Expected `Poeple` not to include the member `#{user.dn}`"
58
+ end
59
+
60
+ def test_user_without_group
61
+ options = @options.merge(:user_groups => %w(People))
62
+ ldap = GitHub::Ldap.new(options)
63
+ user = ldap.valid_login?('ldaptest', 'secret')
64
+
65
+ assert !ldap.groups_contain_user?(user.dn),
66
+ "Expected `Poeple` not to include the member `#{user.dn}`"
67
+ end
68
+
69
+ def test_authenticate_doesnt_return_invalid_users
70
+ user = @ldap.authenticate!('calavera', 'secret')
71
+ assert_equal 'uid=calavera,dc=github,dc=com', user.dn
72
+ end
73
+
74
+ def test_authenticate_doesnt_return_invalid_users
75
+ assert !@ldap.authenticate!('calavera', 'foo'),
76
+ "Expected `authenticate!` to not return an invalid user"
77
+ end
78
+
79
+ def test_authenticate_check_valid_user_and_groups
80
+ options = @options.merge(:user_groups => %w(Enterprise People))
81
+ ldap = GitHub::Ldap.new(options)
82
+ user = ldap.authenticate!('calavera', 'secret')
83
+
84
+ assert_equal 'uid=calavera,dc=github,dc=com', user.dn
85
+ end
86
+
87
+ def test_authenticate_doesnt_return_valid_users_in_different_groups
88
+ options = @options.merge(:user_groups => %w(People))
89
+ ldap = GitHub::Ldap.new(options)
90
+
91
+ assert !ldap.authenticate!('calavera', 'secret'),
92
+ "Expected `authenticate!` to not return an user"
93
+ end
94
+ end
@@ -0,0 +1,10 @@
1
+ __dir__ = File.expand_path(File.dirname(__FILE__))
2
+ __lib__ = File.expand_path('lib', File.dirname(__FILE__))
3
+
4
+ $LOAD_PATH << __dir__ unless $LOAD_PATH.include?(__dir__)
5
+ $LOAD_PATH << __lib__ unless $LOAD_PATH.include?(__lib__)
6
+
7
+ require 'github/ldap'
8
+ require 'github/ldap/server'
9
+
10
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github-ldap
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Calavera
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ldap
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.2.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.2.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: ladle
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: minitest
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '5'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '5'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Ldap authentication for humans
95
+ email:
96
+ - david.calavera@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - Gemfile
103
+ - LICENSE.txt
104
+ - README.md
105
+ - Rakefile
106
+ - github-ldap.gemspec
107
+ - lib/github/ldap.rb
108
+ - lib/github/ldap/fixtures.ldif
109
+ - lib/github/ldap/server.rb
110
+ - test/ldap_test.rb
111
+ - test/test_helper.rb
112
+ homepage: https://github.com/github/github-ldap
113
+ licenses:
114
+ - MIT
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.23
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Ldap client authentication wrapper without all the boilerplate
137
+ test_files:
138
+ - test/ldap_test.rb
139
+ - test/test_helper.rb
140
+ has_rdoc: