rims-passwd-ldap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ # local OpenLDAP for rims-passwd-ldap
2
+
3
+ FROM ubuntu:18.04
4
+ MAINTAINER TOKI Yoshinori
5
+
6
+ RUN apt-get update
7
+
8
+ # slapd admin
9
+ ADD auth.yml auth.yml
10
+ RUN awk '/^pass:/{print "slapd","slapd/password1","password",$2}' auth.yml | debconf-set-selections
11
+ RUN awk '/^pass:/{print "slapd","slapd/password2","password",$2}' auth.yml | debconf-set-selections
12
+
13
+ # slapd and some useful tools
14
+ RUN apt-get -y install slapd ldap-utils rsyslog
15
+
16
+ # slapd setup
17
+ ADD modconf.ldif modconf.ldif
18
+ RUN service slapd start && ldapmodify -Y EXTERNAL -H ldapi:/// -f modconf.ldif && service slapd stop
19
+
20
+ CMD service rsyslog start && service slapd start && bash
21
+ EXPOSE 389
@@ -0,0 +1 @@
1
+ pass: 2dbb92b1-1fbb-43a2-9390-fe799b6fe8b9
@@ -0,0 +1,16 @@
1
+ dn: cn=config
2
+ changetype: modify
3
+ replace: olcLogLevel
4
+ olcLogLevel: 64 256
5
+
6
+ dn: cn=module{0},cn=config
7
+ changetype: modify
8
+ add: olcModuleLoad
9
+ olcModuleLoad: memberof
10
+
11
+ dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
12
+ changetype: add
13
+ objectClass: olcMemberOf
14
+ objectClass: olcOverlayConfig
15
+ objectClass: olcConfig
16
+ objectClass: top
@@ -0,0 +1,3 @@
1
+ name: rims-slapd
2
+ repository: toki/rims-slapd
3
+ tag: v3.0
data/docker/users.yml ADDED
@@ -0,0 +1,44 @@
1
+ support:
2
+ - cn: search
3
+ userPassword: 4586d51f-2161-4fb2-a20a-9e03eaeba1e0
4
+ user:
5
+ - uid: einstein
6
+ userPassword: cdb1a742-398f-4717-9fcd-fd700f734854
7
+ cn: Albert Einstein
8
+ sn: Einstein
9
+ group: physics
10
+ - uid: feynman
11
+ userPassword: 0ee33778-9b59-4983-a684-99d7e38c91e1
12
+ cn: Richard Phillips Feynman
13
+ sn: Feynman
14
+ group: physics
15
+ - uid: landau
16
+ userPassword: 35c40788-62c4-4b35-ba10-df737fabea99
17
+ cn: Lev Davidovich Landau
18
+ sn: Landau
19
+ group: physics
20
+ - uid: hawking
21
+ userPassword: b745ad68-dc3c-4b51-a730-590fb8f659f6
22
+ cn: Stephen William Hawking
23
+ sn: Hawking
24
+ group: physics
25
+ - uid: galileo
26
+ userPassword: aad39384-8888-46cd-825b-a2b4f2713a12
27
+ cn: Galileo Galilei
28
+ sn: Galilei
29
+ group: physics
30
+ - uid: galois
31
+ userPassword: d4af8bc9-fac8-4242-b7ac-42efefb253b9
32
+ cn: Evariste Galois
33
+ sn: Galois
34
+ group: mathematics
35
+ - uid: euler
36
+ userPassword: 2291ffeb-b1cd-478c-9625-ebd2746595f4
37
+ cn: Leonhard Euler
38
+ sn: Euler
39
+ group: mathematics
40
+ - uid: gauss
41
+ userPassword: 3f5c5559-b262-4c51-88ae-60e7c3022a36
42
+ cn: Carolus Fridericus Gauss
43
+ sn: Gauss
44
+ group: mathematics
@@ -0,0 +1,223 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'net/ldap'
4
+ require 'rims'
5
+ require 'rims/passwd/ldap/version'
6
+ require 'uri'
7
+
8
+ # to enable LDAP pass-source plug-in, add the entry of
9
+ # <tt>rims/passwd/ldap</tt> to <tt>load_libraries</tt> list.
10
+ #
11
+ # ex.
12
+ # load_libraries:
13
+ # - rims/passwd/ldap
14
+ #
15
+ class RIMS::Password::LDAPSource < RIMS::Password::Source
16
+ def initialize(host, port, base_dn, attr, scope: 'sub', filter: nil,
17
+ search_bind_auth: { :method => :anonymous },
18
+ search_bind_verification_skip: false,
19
+ encryption: false)
20
+ @host = host
21
+ @port = port
22
+ @base_dn = base_dn
23
+ @attr = attr
24
+ @scope_src = scope
25
+ @filter_src = filter
26
+ @search_bind_auth = search_bind_auth
27
+ @search_bind_verification_skip = search_bind_verification_skip
28
+ @encryption = encryption
29
+ end
30
+
31
+ def start
32
+ scheme = @encryption ? 'ldaps' : 'ldap'
33
+ @logger.info("LDAP pass-source: #{scheme}://#{@host}:#{@port}/#{@base_dn}?#{@attr}?#{@scope_src}?#{@filter_src}")
34
+
35
+ case (@scope_src)
36
+ when 'base'
37
+ @scope = Net::LDAP::SearchScope_BaseObject
38
+ when 'one'
39
+ @scope = Net::LDAP::SearchScope_SingleLevel
40
+ when 'sub'
41
+ @scope = Net::LDAP::SearchScope_WholeSubtree
42
+ else
43
+ raise "unknown ldap search scope: #{@scope_src}"
44
+ end
45
+
46
+ if (@filter_src) then
47
+ filter = Net::LDAP::Filter.construct(@filter_src)
48
+ @filter_factory = proc{|username|
49
+ Net::LDAP::Filter.eq(@attr, username) & filter
50
+ }
51
+ else
52
+ @filter_factory = proc{|username|
53
+ Net::LDAP::Filter.eq(@attr, username)
54
+ }
55
+ end
56
+ end
57
+
58
+ def raw_password?
59
+ false
60
+ end
61
+
62
+ def ldap_open
63
+ options = { host: @host, port: @port }
64
+ if (@search_bind_verification_skip) then
65
+ options[:auth] = @search_bind_auth
66
+ end
67
+ if (@encryption) then
68
+ options[:encryption] = :simple_tls
69
+ end
70
+
71
+ Net::LDAP.open(options) {|ldap|
72
+ unless (@search_bind_verification_skip) then
73
+ # implicit bind of Net::LDAP.open has no error handling.
74
+ # explicit 2nd bind is required to check bind error.
75
+ if (@logger.debug?) then
76
+ auth = @search_bind_auth.dup
77
+ auth.delete(:password)
78
+ @logger.debug("LDAP bind: #{auth.inspect}")
79
+ end
80
+ ldap.bind(@search_bind_auth) or raise "failed to bind to search: #{ldap.get_operation_result}"
81
+ @logger.debug("LDAP bind OK")
82
+ end
83
+
84
+ yield(ldap)
85
+ }
86
+ end
87
+ private :ldap_open
88
+
89
+ def search(ldap, username)
90
+ user_filter = @filter_factory.call(username)
91
+ @logger.debug("LDAP search #{@base_dn} #{@attr} #{@scope} #{user_filter}") if @logger.debug?
92
+ if (users = ldap.search(base: @base_dn, attributes: [ @attr ], scope: @scope, filter: user_filter)) then
93
+ unless (users.empty?) then
94
+ user_dn = users.first.dn
95
+ @logger.info("found a LDAP user: #{user_dn}")
96
+ return user_dn
97
+ else
98
+ @logger.debug("LDAP search result: not found") if @logger.debug?
99
+ end
100
+ else
101
+ @logger.debug("LDAP search result: no entries") if @logger.debug?
102
+ end
103
+
104
+ nil
105
+ end
106
+ private :search
107
+
108
+ def user?(username)
109
+ ldap_open{|ldap|
110
+ if (search(ldap, username)) then
111
+ true
112
+ else
113
+ false
114
+ end
115
+ }
116
+ end
117
+
118
+ def compare_password(username, password)
119
+ ldap_open{|ldap|
120
+ if (user_dn = search(ldap, username)) then
121
+ if (ldap.bind(method: :simple, username: user_dn, password: password)) then
122
+ true
123
+ else
124
+ false
125
+ end
126
+ end
127
+ }
128
+ end
129
+
130
+ class << self
131
+ def uri_decode(string)
132
+ string.gsub(/%(\h)(\h)/) { [$&[1, 2].hex].pack('C') }.force_encoding(string.encoding)
133
+ end
134
+
135
+ def parse_uri(uri_string)
136
+ ldap_params = {}
137
+
138
+ ldap_uri = URI.parse(uri_string)
139
+ case (ldap_uri)
140
+ when URI::LDAPS
141
+ ldap_params[:encryption] = true
142
+ when URI::LDAP
143
+ # OK
144
+ else
145
+ raise "not a LDAP URI: #{uri_string}"
146
+ end
147
+
148
+ ldap_params[:host] = ldap_uri.host || 'localhost'
149
+ ldap_params[:port] = ldap_uri.port or raise "required LDAP port: #{uri_string}"
150
+ ldap_params[:base_dn] = uri_decode(ldap_uri.dn) if (ldap_uri.dn && ! ldap_uri.dn.empty?)
151
+ ldap_params[:attribute] = uri_decode(ldap_uri.attributes) if ldap_uri.attributes
152
+ ldap_params[:scope] = uri_decode(ldap_uri.scope) if ldap_uri.scope
153
+ ldap_params[:filter] = uri_decode(ldap_uri.filter) if ldap_uri.filter
154
+
155
+ ldap_params
156
+ end
157
+
158
+ # configuration entries:
159
+ # * <tt>"ldap_uri"</tt>
160
+ # * <tt>"base_dn"</tt>
161
+ # * <tt>"attribute"</tt>
162
+ # * <tt>"scope"</tt>
163
+ # * <tt>"filter"</tt>
164
+ # * <tt>"search_bind_auth"</tt>
165
+ # * <tt>"method"</tt>
166
+ # * <tt>"username"</tt>
167
+ # * <tt>"password"</tt>
168
+ # * <tt>"search_bind_verification_skip"</tt>
169
+ #
170
+ def build_from_conf(config)
171
+ unless (config.key? 'ldap_uri') then
172
+ raise 'required ldap_uri parameter at LDAP pass-source configuration.'
173
+ end
174
+ ldap_params = parse_uri(config['ldap_uri'])
175
+ ldap_args = []
176
+
177
+ for name in [ :host, :port ]
178
+ value = ldap_params.delete(name) or raise "internal error: #{name}"
179
+ ldap_args << value
180
+ end
181
+
182
+ for name in [ :base_dn, :attribute ]
183
+ value = ldap_params.delete(name)
184
+ if (config.key? name.to_s) then
185
+ value = config[name.to_s]
186
+ end
187
+ unless (value) then
188
+ raise "required #{name} parameter at LDAP pass-source configuration."
189
+ end
190
+ ldap_args << value
191
+ end
192
+
193
+ for name in [ :scope, :filter, :search_bind_verification_skip ]
194
+ if (config.key? name.to_s) then
195
+ ldap_params[name] = config[name.to_s]
196
+ end
197
+ end
198
+
199
+ if (config.key? 'search_bind_auth') then
200
+ case (config['search_bind_auth']['method'])
201
+ when 'anonymous'
202
+ auth = { method: :anonymous }
203
+ when 'simple'
204
+ auth = { method: :simple }
205
+ auth[:username] = config['search_bind_auth']['username'] or raise 'required serach bind username at LDAP pass-source configuration.'
206
+ auth[:password] = config['search_bind_auth']['password'] or raise 'required search bind password at LDAP pass-source configuration.'
207
+ else
208
+ raise "unknown or unsupported bind method type: #{config['search_bind_auth'].inspect}"
209
+ end
210
+ ldap_params[:search_bind_auth] = auth
211
+ end
212
+
213
+ self.new(*ldap_args, **ldap_params)
214
+ end
215
+ end
216
+
217
+ RIMS::Authentication.add_plug_in('ldap', self)
218
+ end
219
+
220
+ # Local Variables:
221
+ # mode: Ruby
222
+ # indent-tabs-mode: nil
223
+ # End:
@@ -0,0 +1,10 @@
1
+ #-*- coding: utf-8 -*-
2
+
3
+ module RIMS
4
+ Password_LDAPSource_VERSION = '0.0.1'
5
+ end
6
+
7
+ # Local Variables:
8
+ # mode: Ruby
9
+ # indent-tabs-mode: nil
10
+ # End:
@@ -0,0 +1,36 @@
1
+ #-*- coding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'rims/passwd/ldap/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "rims-passwd-ldap"
9
+ spec.version = RIMS::Password_LDAPSource_VERSION
10
+ spec.authors = ["TOKI Yoshinori"]
11
+ spec.email = ["toki@freedom.ne.jp"]
12
+ spec.summary = %q{RIMS password source plug-in for LDAP authentication}
13
+ spec.description = <<-'EOF'
14
+ RIMS password source plug-in for LDAP authentication.
15
+ By introducing this plug-in, RIMS IMAP server will be able to
16
+ authenticate users with LDAP.
17
+ EOF
18
+ spec.homepage = "https://github.com/y10k/rims-passwd-ldap"
19
+ spec.license = "MIT"
20
+
21
+ spec.files = `git ls-files -z`.split("\x0")
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_runtime_dependency "rims", ">= 0.2.0"
27
+ spec.add_runtime_dependency "net-ldap"
28
+ spec.add_development_dependency "bundler", "~> 1.7"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "test-unit"
31
+ end
32
+
33
+ # Local Variables:
34
+ # mode: Ruby
35
+ # indent-tabs-mode: nil
36
+ # End:
@@ -0,0 +1,614 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'json'
4
+ require 'logger'
5
+ require 'net/ldap'
6
+ require 'pp' if $DEBUG
7
+ require 'rims/passwd/ldap'
8
+ require 'test/unit'
9
+ require 'uri'
10
+ require 'yaml'
11
+
12
+ module RIMS::Password::LDAPSource::Test
13
+ module LDAPExample
14
+ DOCKER = ENV['DOCKER_COMMAND'] || 'docker'
15
+ AUTH = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'docker', 'build', 'auth.yml'))
16
+ CONTAINER = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'docker', 'container.yml'))
17
+ HOST = (ENV.key? 'DOCKER_HOST') ? URI(ENV['DOCKER_HOST']).host : 'localhost'
18
+ conf = JSON.parse(`#{DOCKER} inspect #{CONTAINER['name']}`)
19
+ exposed_port = conf[0]['Config']['ExposedPorts'].keys.first
20
+ published_port = Integer(conf[0]['NetworkSettings']['Ports'][exposed_port][0]['HostPort'])
21
+ PORT = published_port
22
+ USERS = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'docker', 'users.yml'))
23
+ SEARCH = USERS['support'].find{|role| role['cn'] == 'search' }
24
+ SEARCH_USER = "cn=#{SEARCH['cn']},ou=support,o=science,dc=nodomain"
25
+ SEARCH_PASS = SEARCH['userPassword']
26
+ end
27
+
28
+ module LDAPSourceTestMethod
29
+ include LDAPExample
30
+
31
+ def setup
32
+ @logger = Logger.new(STDOUT)
33
+ @logger.level = ($DEBUG) ? Logger::DEBUG : Logger::FATAL
34
+ @search_bind_verification_skip = false
35
+ @search_bind_auth = {
36
+ method: :simple,
37
+ username: SEARCH_USER,
38
+ password: SEARCH_PASS
39
+ }
40
+ end
41
+
42
+ def open_ldap_src(ldap_uri, search_bind_auth: @search_bind_auth)
43
+ ldap_uri = URI.parse(ldap_uri)
44
+
45
+ host = ldap_uri.host
46
+ port = ldap_uri.port
47
+ base_dn = ldap_uri.dn
48
+ attr = ldap_uri.attributes
49
+
50
+ optional = {}
51
+ optional[:scope] = ldap_uri.scope if ldap_uri.scope
52
+ optional[:filter] = ldap_uri.filter if ldap_uri.filter
53
+ optional[:search_bind_auth] = search_bind_auth if search_bind_auth
54
+ optional[:search_bind_verification_skip] = @search_bind_verification_skip
55
+ case (ldap_uri.scheme)
56
+ when 'ldap'
57
+ # ok
58
+ when 'ldaps'
59
+ optional[:encryption] = true
60
+ else
61
+ raise "unknown URI scheme: #{ldap_uri}"
62
+ end
63
+
64
+ ldap_src = RIMS::Password::LDAPSource.new(host, port, base_dn, attr, **optional)
65
+ ldap_src.logger = @logger
66
+
67
+ ldap_src.start
68
+ begin
69
+ yield(ldap_src)
70
+ ensure
71
+ ldap_src.stop
72
+ end
73
+ end
74
+ private :open_ldap_src
75
+
76
+ def test_raw_password?
77
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid") {|ldap_src|
78
+ assert_false(ldap_src.raw_password?)
79
+ }
80
+ end
81
+
82
+ def test_users
83
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid") {|ldap_src|
84
+ for user in USERS['user']
85
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
86
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
87
+ end
88
+ }
89
+ end
90
+
91
+ def test_users_wrong_password
92
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid") {|ldap_src|
93
+ for user in USERS['user']
94
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
95
+ assert_false(ldap_src.compare_password(user['uid'], 'invalid_pass'), "uid: #{user.inspect}")
96
+ end
97
+ }
98
+ end
99
+
100
+ def test_users_wrong_base_dn
101
+ open_ldap_src("ldap://#{HOST}:#{PORT}/o=science,dc=nodomain?uid") {|ldap_src|
102
+ for user in USERS['user']
103
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
104
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
105
+ end
106
+ }
107
+
108
+ open_ldap_src("ldap://#{HOST}:#{PORT}/o=no_org,dc=nodomain?uid") {|ldap_src|
109
+ for user in USERS['user']
110
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
111
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
112
+ end
113
+ }
114
+ end
115
+
116
+ def test_users_scope_base
117
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid?base") {|ldap_src|
118
+ for user in USERS['user']
119
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
120
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
121
+ end
122
+ }
123
+
124
+ for user in USERS['user']
125
+ open_ldap_src("ldap://#{HOST}:#{PORT}/uid=#{user['uid']},ou=user,o=science,dc=nodomain?uid?base") {|ldap_src|
126
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
127
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
128
+ }
129
+ end
130
+ end
131
+
132
+ def test_users_scope_one
133
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid?one") {|ldap_src|
134
+ for user in USERS['user']
135
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
136
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
137
+ end
138
+ }
139
+
140
+ for user in USERS['user']
141
+ open_ldap_src("ldap://#{HOST}:#{PORT}/uid=#{user['uid']},ou=user,o=science,dc=nodomain?uid?one") {|ldap_src|
142
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
143
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
144
+ }
145
+ end
146
+ end
147
+
148
+ def test_users_scope_sub
149
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid?sub") {|ldap_src|
150
+ for user in USERS['user']
151
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
152
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
153
+ end
154
+ }
155
+
156
+ for user in USERS['user']
157
+ open_ldap_src("ldap://#{HOST}:#{PORT}/uid=#{user['uid']},ou=user,o=science,dc=nodomain?uid?sub") {|ldap_src|
158
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
159
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
160
+ }
161
+ end
162
+ end
163
+
164
+ def test_users_scope_invalid_error
165
+ assert_raise(RuntimeError) {
166
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid?unknown") {|ldap_src|
167
+ flunk
168
+ }
169
+ }
170
+ end
171
+
172
+ def test_users_filter_physics
173
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid??(memberOf=cn=physics,ou=group,o=science,dc=nodomain)") {|ldap_src|
174
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
175
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
176
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
177
+ end
178
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
179
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
180
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
181
+ end
182
+ }
183
+ end
184
+
185
+ def test_users_filter_mathematics
186
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid??(memberOf=cn=mathmatics,ou=group,o=science,dc=nodomain)") {|ldap_src|
187
+ for user in USERS['user'].find_all{|user| user['group'] == 'mathmatics' }
188
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
189
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
190
+ end
191
+ for user in USERS['user'].find_all{|user| user['group'] != 'mathmatics' }
192
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
193
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
194
+ end
195
+ }
196
+ end
197
+
198
+ def test_users_filter_invalid
199
+ assert_raise(Net::LDAP::FilterSyntaxInvalidError) {
200
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid??unknown") {|ldap_src|
201
+ flunk
202
+ }
203
+ }
204
+ end
205
+ end
206
+
207
+ class LDAPSourceTest < Test::Unit::TestCase
208
+ include LDAPSourceTestMethod
209
+
210
+ def test_wrong_search_bind
211
+ auth = {
212
+ method: :simple,
213
+ username: 'no_dn',
214
+ password: ''
215
+ }
216
+ assert_raise(RuntimeError) {
217
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
218
+ ldap_src.user? 'foo' # to bind
219
+ flunk
220
+ }
221
+ }
222
+
223
+ auth = {
224
+ method: :simple,
225
+ username: 'cn=no_role,ou=support,o=science,dc=nodomain',
226
+ password: 'open_sesame'
227
+ }
228
+ assert_raise(RuntimeError) {
229
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
230
+ ldap_src.user? 'foo' # to bind
231
+ flunk
232
+ }
233
+ }
234
+
235
+ auth = {
236
+ method: :simple,
237
+ username: "cn=#{SEARCH['cn']},ou=support,o=science,dc=nodomain",
238
+ password: 'invalid_pass'
239
+ }
240
+ assert_raise(RuntimeError) {
241
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
242
+ ldap_src.user? 'foo' # to bind
243
+ flunk
244
+ }
245
+ }
246
+ end
247
+ end
248
+
249
+ class LDAPSourceSearchBindVerificationSkipTest < Test::Unit::TestCase
250
+ include LDAPSourceTestMethod
251
+
252
+ def setup
253
+ super
254
+ @search_bind_verification_skip = true
255
+ end
256
+
257
+ def test_wrong_search_bind_no_error
258
+ auth = {
259
+ method: :simple,
260
+ username: 'no_dn',
261
+ password: ''
262
+ }
263
+ #assert_raise(RuntimeError) {
264
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
265
+ ldap_src.user? 'foo' # to bind
266
+ #flunk
267
+ }
268
+ #}
269
+
270
+ auth = {
271
+ method: :simple,
272
+ username: 'cn=no_role,ou=support,o=science,dc=nodomain',
273
+ password: 'open_sesame'
274
+ }
275
+ #assert_raise(RuntimeError) {
276
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
277
+ ldap_src.user? 'foo' # to bind
278
+ #flunk
279
+ }
280
+ #}
281
+
282
+ auth = {
283
+ method: :simple,
284
+ username: "cn=#{SEARCH['cn']},ou=support,o=science,dc=nodomain",
285
+ password: 'invalid_pass'
286
+ }
287
+ #assert_raise(RuntimeError) {
288
+ open_ldap_src("ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid", search_bind_auth: auth) {|ldap_src|
289
+ ldap_src.user? 'foo' # to bind
290
+ #flunk
291
+ }
292
+ #}
293
+ end
294
+ end
295
+
296
+ class LDAPSourceConfigTest < Test::Unit::TestCase
297
+ include LDAPExample
298
+
299
+ def assert_decode_uri(expected_string, src_string)
300
+ assert_equal(expected_string, RIMS::Password::LDAPSource.uri_decode(src_string))
301
+ end
302
+ private :assert_decode_uri
303
+
304
+ def test_uri_decode
305
+ assert_decode_uri('ou=user,o=science,dc=nodomain', 'ou=user,o=science,dc=nodomain')
306
+ assert_decode_uri('(cn=Albert Einstein)', '(cn=Albert%20Einstein)')
307
+ assert_decode_uri('(&(cn=Albert Einstein)(memberOf=cn=physics,ou=group,o=science,dc=nodomain))',
308
+ '(%26(cn=Albert%20Einstein)(memberOf=cn=physics%2cou=group%2co=science%2cdc=nodomain))')
309
+ assert_decode_uri('+', '+')
310
+ end
311
+
312
+ def assert_parse_uri(expected_ldap_params, ldap_uri)
313
+ assert_equal(expected_ldap_params, RIMS::Password::LDAPSource.parse_uri(ldap_uri))
314
+ end
315
+ private :assert_parse_uri
316
+
317
+ def test_parse_uri
318
+ assert_parse_uri({ host: 'localhost', port: 389 }, 'ldap:///')
319
+ assert_parse_uri({ host: 'localhost', port: 636, encryption: true }, 'ldaps://')
320
+ assert_parse_uri({ host: 'mydomain', port: 389 }, 'ldap://mydomain')
321
+ assert_parse_uri({ host: 'mydomain', port: 38900 }, 'ldap://mydomain:38900')
322
+ assert_parse_uri({ host: 'mydomain',
323
+ port: 389,
324
+ base_dn: 'ou=user,o=science,dc=nodomain'
325
+ }, 'ldap://mydomain/ou=user,o=science,dc=nodomain')
326
+ assert_parse_uri({ host: 'mydomain',
327
+ port: 389,
328
+ base_dn: 'ou=user, o=science, dc=nodomain'
329
+ }, 'ldap://mydomain/ou=user,%20o=science,%20dc=nodomain')
330
+ assert_parse_uri({ host: 'mydomain', port: 389, attribute: 'uid' }, 'ldap://mydomain/?uid')
331
+ assert_parse_uri({ host: 'mydomain', port: 389, attribute: '?foo' }, 'ldap://mydomain/?%3ffoo')
332
+ assert_parse_uri({ host: 'mydomain', port: 389, scope: 'base' }, 'ldap://mydomain/??base')
333
+ assert_parse_uri({ host: 'mydomain', port: 389, scope: 'one' }, 'ldap://mydomain/??one')
334
+ assert_parse_uri({ host: 'mydomain', port: 389, scope: 'sub' }, 'ldap://mydomain/??sub')
335
+ assert_parse_uri({ host: 'mydomain', port: 389, filter: '(uid=einstein)' }, 'ldap://mydomain/???(uid=einstein)')
336
+ assert_parse_uri({ host: 'mydomain', port: 389, filter: '(cn=Albert Einstein)' }, 'ldap://mydomain/???(cn=Albert%20Einstein)')
337
+ assert_parse_uri({ host: 'mydomain',
338
+ port: 6360,
339
+ encryption: true,
340
+ base_dn: 'ou=user,o=science,dc=nodomain',
341
+ attribute: 'uid',
342
+ scope: 'base',
343
+ filter: '(uid=einstein)'
344
+ }, 'ldaps://mydomain:6360/ou=user,o=science,dc=nodomain?uid?base?(uid=einstein)')
345
+ end
346
+
347
+ def build_from_conf(config)
348
+ ldap_src = RIMS::Password::LDAPSource.build_from_conf(config)
349
+
350
+ logger = Logger.new(STDOUT)
351
+ logger.level = ($DEBUG) ? Logger::DEBUG : Logger::FATAL
352
+ ldap_src.logger = logger
353
+
354
+ ldap_src.start
355
+ begin
356
+ yield(ldap_src)
357
+ ensure
358
+ ldap_src.stop
359
+ end
360
+ end
361
+ private :build_from_conf
362
+
363
+ def test_build_from_conf
364
+ c = {
365
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
366
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
367
+ 'attribute' => 'uid',
368
+ 'scope' => 'one',
369
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
370
+ 'search_bind_auth' => {
371
+ 'method' => 'simple',
372
+ 'username' => SEARCH_USER,
373
+ 'password' => SEARCH_PASS
374
+ },
375
+ 'search_bind_verification_skip' => false
376
+ }
377
+ build_from_conf(c) {|ldap_src|
378
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
379
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
380
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
381
+ end
382
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
383
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
384
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
385
+ end
386
+ }
387
+ end
388
+
389
+ def test_build_from_conf_ldap_uri
390
+ c = {
391
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}/ou=user,o=science,dc=nodomain?uid?one?(memberOf=cn=physics,ou=group,o=science,dc=nodomain)",
392
+ 'search_bind_auth' => {
393
+ 'method' => 'simple',
394
+ 'username' => SEARCH_USER,
395
+ 'password' => SEARCH_PASS
396
+ },
397
+ 'search_bind_verification_skip' => false
398
+ }
399
+ build_from_conf(c) {|ldap_src|
400
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
401
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
402
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
403
+ end
404
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
405
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
406
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
407
+ end
408
+ }
409
+ end
410
+
411
+ def test_build_from_conf_error_no_ldap_uri
412
+ c = {
413
+ #'ldap_uri' => "ldap://#{HOST}:#{PORT}",
414
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
415
+ 'attribute' => 'uid',
416
+ 'scope' => 'one',
417
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
418
+ 'search_bind_auth' => {
419
+ 'method' => 'simple',
420
+ 'username' => SEARCH_USER,
421
+ 'password' => SEARCH_PASS
422
+ },
423
+ 'search_bind_verification_skip' => false
424
+ }
425
+ assert_raise(RuntimeError) {
426
+ build_from_conf(c) {|ldap_src|
427
+ flunk
428
+ }
429
+ }
430
+ end
431
+
432
+ def test_build_from_conf_error_no_base_dn
433
+ c = {
434
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
435
+ #'base_dn' => 'ou=user,o=science,dc=nodomain',
436
+ 'attribute' => 'uid',
437
+ 'scope' => 'one',
438
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
439
+ 'search_bind_auth' => {
440
+ 'method' => 'simple',
441
+ 'username' => SEARCH_USER,
442
+ 'password' => SEARCH_PASS
443
+ },
444
+ 'search_bind_verification_skip' => false
445
+ }
446
+ assert_raise(RuntimeError) {
447
+ build_from_conf(c) {|ldap_src|
448
+ flunk
449
+ }
450
+ }
451
+ end
452
+
453
+ def test_build_from_conf_error_no_attribute
454
+ c = {
455
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
456
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
457
+ #'attribute' => 'uid',
458
+ 'scope' => 'one',
459
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
460
+ 'search_bind_auth' => {
461
+ 'method' => 'simple',
462
+ 'username' => SEARCH_USER,
463
+ 'password' => SEARCH_PASS
464
+ },
465
+ 'search_bind_verification_skip' => false
466
+ }
467
+ assert_raise(RuntimeError) {
468
+ build_from_conf(c) {|ldap_src|
469
+ flunk
470
+ }
471
+ }
472
+ end
473
+
474
+ def test_build_from_conf_scope_default
475
+ c = {
476
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
477
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
478
+ 'attribute' => 'uid',
479
+ #'scope' => 'one',
480
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
481
+ 'search_bind_auth' => {
482
+ 'method' => 'simple',
483
+ 'username' => SEARCH_USER,
484
+ 'password' => SEARCH_PASS
485
+ },
486
+ 'search_bind_verification_skip' => false
487
+ }
488
+ build_from_conf(c) {|ldap_src|
489
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
490
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
491
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
492
+ end
493
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
494
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
495
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
496
+ end
497
+ }
498
+ end
499
+
500
+ def test_build_from_conf_no_filter
501
+ c = {
502
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
503
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
504
+ 'attribute' => 'uid',
505
+ 'scope' => 'one',
506
+ #'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
507
+ 'search_bind_auth' => {
508
+ 'method' => 'simple',
509
+ 'username' => SEARCH_USER,
510
+ 'password' => SEARCH_PASS
511
+ },
512
+ 'search_bind_verification_skip' => false
513
+ }
514
+ build_from_conf(c) {|ldap_src|
515
+ for user in USERS['user']
516
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
517
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
518
+ end
519
+ }
520
+ end
521
+
522
+ def test_build_from_conf_search_bind_verification_skip_default
523
+ c = {
524
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
525
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
526
+ 'attribute' => 'uid',
527
+ 'scope' => 'one',
528
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
529
+ 'search_bind_auth' => {
530
+ 'method' => 'simple',
531
+ 'username' => SEARCH_USER,
532
+ 'password' => SEARCH_PASS
533
+ },
534
+ #'search_bind_verification_skip' => false
535
+ }
536
+ build_from_conf(c) {|ldap_src|
537
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
538
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
539
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
540
+ end
541
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
542
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
543
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
544
+ end
545
+ }
546
+ end
547
+
548
+ def test_build_from_conf_search_bind_verification_skip_enabled
549
+ c = {
550
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
551
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
552
+ 'attribute' => 'uid',
553
+ 'scope' => 'one',
554
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
555
+ 'search_bind_auth' => {
556
+ 'method' => 'simple',
557
+ 'username' => SEARCH_USER,
558
+ 'password' => SEARCH_PASS
559
+ },
560
+ 'search_bind_verification_skip' => true
561
+ }
562
+ build_from_conf(c) {|ldap_src|
563
+ for user in USERS['user'].find_all{|user| user['group'] == 'physics' }
564
+ assert_true((ldap_src.user? user['uid']), "uid: #{user.inspect}")
565
+ assert_true(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
566
+ end
567
+ for user in USERS['user'].find_all{|user| user['group'] != 'physics' }
568
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
569
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
570
+ end
571
+ }
572
+ end
573
+
574
+ def test_build_from_conf_search_bind_auth_default_anonymous
575
+ c = {
576
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
577
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
578
+ 'attribute' => 'uid',
579
+ 'scope' => 'one',
580
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
581
+ 'search_bind_verification_skip' => false
582
+ }
583
+ build_from_conf(c) {|ldap_src|
584
+ for user in USERS['user']
585
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
586
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
587
+ end
588
+ }
589
+ end
590
+
591
+ def test_build_from_conf_search_bind_auth_explicit_anonymous
592
+ c = {
593
+ 'ldap_uri' => "ldap://#{HOST}:#{PORT}",
594
+ 'base_dn' => 'ou=user,o=science,dc=nodomain',
595
+ 'attribute' => 'uid',
596
+ 'scope' => 'one',
597
+ 'filter' => '(memberOf=cn=physics,ou=group,o=science,dc=nodomain)',
598
+ 'search_bind_auth' => { 'method' => 'anonymous' },
599
+ 'search_bind_verification_skip' => false
600
+ }
601
+ build_from_conf(c) {|ldap_src|
602
+ for user in USERS['user']
603
+ assert_false((ldap_src.user? user['uid']), "uid: #{user.inspect}")
604
+ assert_nil(ldap_src.compare_password(user['uid'], user['userPassword']), "uid: #{user.inspect}")
605
+ end
606
+ }
607
+ end
608
+ end
609
+ end
610
+
611
+ # Local Variables:
612
+ # mode: Ruby
613
+ # indent-tabs-mode: nil
614
+ # End: