rims-passwd-ldap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: