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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c8a4a2fe99b941f01a670ea925ca3f7f365f04607d3b459c1e1ebd427059aaed
4
+ data.tar.gz: b80e434c472500706151588e93e8db1a041b6d3899b38054567f48f390bcc68c
5
+ SHA512:
6
+ metadata.gz: 2bc92fe90f1440e75b8aa6cc368a7a9aecc5c0b85206d325f8e89357493d31fb86bf215d49f7fbf6b3dbb3d4a32cdd36dde38823c3d4b9ac217f967616f6ecf4
7
+ data.tar.gz: 39bc750dd4f65f1c5804cde8269c710330a99ccfdc9f43cc82b431842f415300755121920d86857ae2c8465570dae8bf944a4f240f3acaaaefe61a273939d25c
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in rims-passwd-ldap.gemspec
6
+ gemspec
7
+
8
+ # Local Variables:
9
+ # mode: Ruby
10
+ # indent-tabs-mode: nil
11
+ # End:
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015-2016 TOKI Yoshinori
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,54 @@
1
+ RIMS::Password::LDAPSource
2
+ ==========================
3
+
4
+ RIMS password source plug-in for LDAP authentication.
5
+ By introducing this plug-in, RIMS IMAP server will be able to
6
+ authenticate users with LDAP.
7
+
8
+ Installation
9
+ ------------
10
+
11
+ Add this line to your application's Gemfile that includes RIMS:
12
+
13
+ ```ruby
14
+ gem 'rims-passwd-ldap'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install rims-passwd-ldap
24
+
25
+ Usage
26
+ -----
27
+
28
+ Add these lines to your config.yml of RIMS:
29
+
30
+ ```yaml
31
+ load_libraries:
32
+ - rims/passwd/ldap
33
+ authentication:
34
+ - plug_in: ldap
35
+ configuration:
36
+ ldap_uri: ldap://localhost:38900 # hostname and port, `ldaps' for tls (not tested)
37
+ base_dn: ou=user,o=science,dc=nodomain # base distingished name to search a user
38
+ attribute: uid # attribute matched to username
39
+ scope: sub # search scope from base dn. `base', `one', or `sub'
40
+ filter: (memberOf=cn=physics,ou=group,o=science,dc=nodomain) # search filter
41
+ search_bind_auth:
42
+ method: simple
43
+ username: cn=search,ou=support,o=science,dc=nodomain # username to search a user
44
+ password: ******** # password to search a user
45
+ ```
46
+
47
+ Contributing
48
+ ------------
49
+
50
+ 1. Fork it ( https://github.com/[my-github-username]/rims-passwd-ldap/fork )
51
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
52
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
53
+ 4. Push to the branch (`git push origin my-new-feature`)
54
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/clean'
5
+ require 'rake/testtask'
6
+ require 'rdoc/task'
7
+
8
+ Rake::TestTask.new do |task|
9
+ if ((ENV.key? 'RUBY_DEBUG') && (! ENV['RUBY_DEBUG'].empty?)) then
10
+ task.ruby_opts << '-d'
11
+ end
12
+ end
13
+
14
+ Rake::RDocTask.new do |rd|
15
+ rd.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+ desc 'Build README.html from markdown source.'
19
+ task :readme => %w[ README.html ]
20
+
21
+ file 'README.html' => [ 'README.md' ] do
22
+ sh "pandoc --from=markdown --to=html5 --standalone --self-contained --css=$HOME/.pandoc/github.css --output=README.html README.md"
23
+ end
24
+ CLOBBER.include 'README.html'
25
+
26
+ namespace :docker do
27
+ desc 'setup docker container for unit-test.'
28
+ task :setup do
29
+ chdir('docker') do
30
+ sh 'rake', 'setup'
31
+ end
32
+ end
33
+
34
+ desc 'reset docker container for unit-test.'
35
+ task :reset do
36
+ chdir('docker') do
37
+ sh 'rake', 'reset'
38
+ end
39
+ end
40
+
41
+ desc 'start docker container for unit-test.'
42
+ task :start do
43
+ chdir('docker') do
44
+ sh 'rake', 'docker:start'
45
+ end
46
+ end
47
+ end
48
+
49
+ # Local Variables:
50
+ # mode: Ruby
51
+ # indent-tabs-mode: nil
52
+ # End:
data/docker/Rakefile ADDED
@@ -0,0 +1,382 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'json'
4
+ require 'pp'
5
+ require 'test/unit/assertions'
6
+ require 'uri'
7
+ require 'yaml'
8
+
9
+ include Test::Unit::Assertions
10
+
11
+ DOCKER = ENV['DOCKER_COMMAND'] || 'docker'
12
+
13
+ AUTH = YAML.load_file(File.join(File.dirname(__FILE__), 'build', 'auth.yml'))
14
+ CONTAINER = YAML.load_file(File.join(File.dirname(__FILE__), 'container.yml'))
15
+
16
+ NAME = CONTAINER['name']
17
+ IMAGE_REPOSITORY = CONTAINER['repository']
18
+ IMAGE_TAG = CONTAINER['tag']
19
+
20
+ DOCKER_INSPECT_CACHE = {}
21
+
22
+ def docker_inspect(name)
23
+ DOCKER_INSPECT_CACHE[name] ||= JSON.parse(`#{DOCKER} inspect #{name}`)
24
+ end
25
+
26
+ def get_exposed_port(name)
27
+ docker_inspect(name)[0]['Config']['ExposedPorts'].keys.first
28
+ end
29
+
30
+ def get_published_port(name)
31
+ Integer(docker_inspect(name)[0]['NetworkSettings']['Ports'][get_exposed_port(name)][0]['HostPort'])
32
+ end
33
+
34
+ def docker_exec_i(*cmd, input_string)
35
+ docker_cmd = [ DOCKER, 'exec', '-i', NAME ] + cmd
36
+ IO.popen(docker_cmd, 'w') {|stdin| stdin << input_string }
37
+ if ($?.exitstatus != 0) then
38
+ raise "FAILED: #{docker_cmd.join(' ')} <\n" + input_string
39
+ end
40
+ end
41
+
42
+ def load_users
43
+ YAML.load_file(File.join(File.dirname(__FILE__), 'users.yml'))
44
+ end
45
+
46
+ def ldap_anon
47
+ require 'net/ldap' # on-demand load
48
+ ldap_host = (ENV.key? 'DOCKER_HOST') ? URI(ENV['DOCKER_HOST']).host : 'localhost'
49
+ Net::LDAP.open(host: ldap_host, port: get_published_port(NAME)) {|ldap|
50
+ yield(ldap)
51
+ }
52
+ end
53
+
54
+ def ldap_open(username='cn=admin,dc=nodomain', password=AUTH['pass'], method=:simple)
55
+ ldap_anon{|ldap|
56
+ unless (ldap.bind(method: method, username: username, password: password)) then
57
+ p ldap.get_operation_result
58
+ raise 'failed to bind.'
59
+ end
60
+ yield(ldap)
61
+ }
62
+ end
63
+
64
+ def ldap_error(ldap)
65
+ pp ldap.get_operation_result
66
+ raise 'ldap fail.'
67
+ end
68
+
69
+ desc 'total setup'
70
+ task :setup => [ :'docker:setup', :wait, :'ldap:setup' ]
71
+
72
+ desc 'total reset'
73
+ task :reset => [ :'docker:reset', :wait, :'ldap:setup' ]
74
+
75
+ task :wait do
76
+ s = 10
77
+ puts "...wait #{s}s..."
78
+ sleep(s)
79
+ end
80
+
81
+ namespace :docker do
82
+ task :get_exposed_port do
83
+ p get_exposed_port(NAME)
84
+ end
85
+
86
+ task :get_published_port do
87
+ p get_published_port(NAME)
88
+ end
89
+
90
+ desc 'all setup'
91
+ task :setup => [ :build, :run ]
92
+
93
+ desc 'all destroy'
94
+ task :destroy => [ :stop, :rm, :rmi ]
95
+
96
+ desc 'reset running container'
97
+ task :reset => [ :stop, :rm, :run ]
98
+
99
+ desc 'build image'
100
+ task :build do
101
+ sh "#{DOCKER} build -t #{IMAGE_REPOSITORY}:#{IMAGE_TAG} build"
102
+ end
103
+
104
+ desc 'remove image'
105
+ task :rmi do
106
+ sh "#{DOCKER} rmi #{IMAGE_REPOSITORY}:#{IMAGE_TAG}"
107
+ end
108
+
109
+ desc 'run new container'
110
+ task :run do
111
+ sh "#{DOCKER} run --name=#{NAME} -itd -P #{IMAGE_REPOSITORY}:#{IMAGE_TAG}"
112
+ end
113
+
114
+ desc 'start container'
115
+ task :start do
116
+ sh "#{DOCKER} start #{NAME}"
117
+ end
118
+
119
+ desc 'stop container'
120
+ task :stop do
121
+ sh "#{DOCKER} stop #{NAME}"
122
+ end
123
+
124
+ desc 'remove container'
125
+ task :rm do
126
+ sh "#{DOCKER} rm #{NAME}"
127
+ end
128
+
129
+ desc 'show slapd configuration'
130
+ task :conf do
131
+ sh "#{DOCKER} exec #{NAME} slapcat -b cn=config"
132
+ end
133
+
134
+ desc 'tail slapd logging'
135
+ task :tail do
136
+ sh "#{DOCKER} exec #{NAME} tail -100f /var/log/syslog"
137
+ end
138
+ end
139
+
140
+ namespace :ldap do
141
+ desc 'setup example'
142
+ task :setup => [ :build, :'conf:acl' ]
143
+
144
+ desc 'dump example dit'
145
+ task :dump do
146
+ ldap_open{|ldap|
147
+ puts '* all'
148
+ pp ldap.search(base: 'dc=nodomain')
149
+
150
+ phys_filter = Net::LDAP::Filter.eq('memberOf', 'cn=physics,ou=group,o=science,dc=nodomain')
151
+ math_filter = Net::LDAP::Filter.eq('memberOf', 'cn=mathematics,ou=group,o=science,dc=nodomain')
152
+
153
+ for f in [ phys_filter, math_filter ]
154
+ puts "* filter: #{f}"
155
+ pp ldap.search(base: 'o=science,dc=nodomain', attributes: %w[ dn ], filter: f)
156
+ end
157
+ }
158
+ end
159
+
160
+ desc 'anonymous'
161
+ task :anonymous do
162
+ ldap_anon{|ldap|
163
+ puts '* all'
164
+ pp ldap.search(base: 'dc=nodomain')
165
+ pp ldap.search(base: 'o=science,dc=nodomain')
166
+ pp ldap.search(base: 'ou=user,o=science,dc=nodomain')
167
+
168
+ phys_filter = Net::LDAP::Filter.eq('memberOf', 'cn=physics,ou=group,o=science,dc=nodomain')
169
+ math_filter = Net::LDAP::Filter.eq('memberOf', 'cn=mathematics,ou=group,o=science,dc=nodomain')
170
+
171
+ for f in [ phys_filter, math_filter ]
172
+ puts "* filter: #{f}"
173
+ pp ldap.search(base: 'o=science,dc=nodomain', attributes: %w[ dn ], filter: f)
174
+ end
175
+ }
176
+ end
177
+
178
+ desc 'search dit'
179
+ task :search do
180
+ users = load_users
181
+ search = users['support'].find{|role| role['cn'] = 'search' }
182
+ search_dn = "cn=#{search['cn']},ou=support,o=science,dc=nodomain"
183
+ ldap_open(search_dn, search['userPassword']) {|ldap|
184
+ puts '* all'
185
+ pp ldap.search(base: 'dc=nodomain')
186
+
187
+ puts '* self'
188
+ pp ldap.search(base: search_dn)
189
+
190
+ phys_filter = Net::LDAP::Filter.eq('memberOf', 'cn=physics,ou=group,o=science,dc=nodomain')
191
+ math_filter = Net::LDAP::Filter.eq('memberOf', 'cn=mathematics,ou=group,o=science,dc=nodomain')
192
+
193
+ for f in [ phys_filter, math_filter ]
194
+ puts "* filter: #{f}"
195
+ pp ldap.search(base: 'ou=user,o=science,dc=nodomain', filter: f)
196
+ end
197
+ }
198
+ end
199
+
200
+ desc 'build exmaple dit'
201
+ task :build do
202
+ ldap_open{|ldap|
203
+ ldap.add(dn: 'o=science,dc=nodomain',
204
+ attributes: {
205
+ objectclass: 'organization'
206
+ }) or ldap_error(ldap)
207
+ ldap.add(dn: 'ou=support,o=science,dc=nodomain',
208
+ attributes: {
209
+ objectclass: 'organizationalUnit'
210
+ }) or ldap_error(ldap)
211
+ ldap.add(dn: 'ou=user,o=science,dc=nodomain',
212
+ attributes: {
213
+ objectclass: 'organizationalUnit'
214
+ }) or ldap_error(ldap)
215
+ ldap.add(dn: 'ou=group,o=science,dc=nodomain',
216
+ attributes: {
217
+ objectclass: 'organizationalUnit'
218
+ }) or ldap_error(ldap)
219
+
220
+ users = load_users
221
+
222
+ for role in users['support']
223
+ attrs = {}
224
+ for name, value in role
225
+ case (name)
226
+ when 'userPassword'
227
+ attrs[:userPassword] = Net::LDAP::Password.generate(:ssha, value)
228
+ else
229
+ attrs[name.to_sym] = value
230
+ end
231
+ end
232
+ attrs[:objectClass] = %w[ organizationalRole simpleSecurityObject ]
233
+ ldap.add(dn: "cn=#{role['cn']},ou=support,o=science,dc=nodomain",
234
+ attributes: attrs) or ldap_error(ldap)
235
+ end
236
+
237
+ for user in users['user']
238
+ attrs = {}
239
+ for name, value in user
240
+ case (name)
241
+ when 'userPassword'
242
+ attrs[:userPassword] = Net::LDAP::Password.generate(:ssha, value)
243
+ when 'group'
244
+ # skip
245
+ else
246
+ attrs[name.to_sym] = value
247
+ end
248
+ end
249
+ attrs[:objectclass] = 'inetOrgPerson'
250
+ ldap.add(dn: "uid=#{user['uid']},ou=user,o=science,dc=nodomain",
251
+ attributes: attrs) or ldap_error(ldap)
252
+ end
253
+
254
+ groups = {}
255
+ for user in users['user']
256
+ groups[user['group']] = [] unless (groups.key? user['group'])
257
+ groups[user['group']] << user['uid']
258
+ end
259
+
260
+ for name, members in groups
261
+ ldap.add(dn: "cn=#{name},ou=group,o=science,dc=nodomain",
262
+ attributes: {
263
+ cn: name,
264
+ objectclass: 'groupOfNames',
265
+ member: members.map{|uid| "uid=#{uid},ou=user,o=science,dc=nodomain" }
266
+ }) or ldap_error(ldap)
267
+ end
268
+ }
269
+ end
270
+
271
+ desc 'some tests'
272
+ task :test => [ :test_user_auth, :test_search_acl ]
273
+
274
+ task :test_user_auth do
275
+ ldap_anon{|ldap|
276
+ users = load_users
277
+ for role in users['support']
278
+ ldap.bind(method: :simple,
279
+ username: "cn=#{role['cn']},ou=support,o=science,dc=nodomain",
280
+ password: role['userPassword']) or ldap_error(ldap)
281
+ end
282
+ for user in users['user']
283
+ ldap.bind(method: :simple,
284
+ username: "uid=#{user['uid']},ou=user,o=science,dc=nodomain",
285
+ password: user['userPassword']) or ldap_error(ldap)
286
+ end
287
+ }
288
+ end
289
+
290
+ task :test_search_acl do
291
+ users = load_users
292
+ search = users['support'].find{|role| role['cn'] = 'search' }
293
+ search_dn = "cn=#{search['cn']},ou=support,o=science,dc=nodomain"
294
+ ldap_open(search_dn, search['userPassword']) {|ldap|
295
+ assert_nil(ldap.search(base: 'dc=nodomain'))
296
+ assert_nil(ldap.search(base: 'o=science,dc=nodomain'))
297
+ assert_nil(ldap.search(base: 'ou=support,o=science,dc=nodomain'))
298
+ assert_nil(ldap.search(base: search_dn))
299
+
300
+ assert(entry_list = ldap.search(base: 'ou=user,o=science,dc=nodomain',
301
+ scope: Net::LDAP::SearchScope_BaseObject))
302
+ assert(ou = entry_list.shift)
303
+ assert_equal(%w[ organizationalUnit ], ou[:objectClass])
304
+ assert_equal(%w[ user ], ou[:ou])
305
+ assert_equal([], ou[:userPassword])
306
+ assert_nil(entry_list.shift)
307
+
308
+ assert(entry_list = ldap.search(base: 'ou=user,o=science,dc=nodomain',
309
+ scope: Net::LDAP::SearchScope_WholeSubtree,
310
+ filter: Net::LDAP::Filter.eq('objectClass', 'inetOrgPerson')))
311
+ assert_equal(users['user'].length, entry_list.length)
312
+ for person in entry_list
313
+ assert_equal(%w[ inetOrgPerson ], person[:objectClass])
314
+ for uid in person[:uid]
315
+ assert(users['user'].find{|user| user['uid'] == uid })
316
+ end
317
+ assert_equal([], person[:userPassword])
318
+ end
319
+ }
320
+ end
321
+ end
322
+
323
+ namespace :conf do
324
+ desc 'setup slapd ACL'
325
+ task :acl do
326
+ docker_exec_i 'ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///', <<EOF
327
+ dn: olcDatabase={1}mdb,cn=config
328
+ changetype: modify
329
+ replace: olcAccess
330
+ olcAccess: {0}
331
+ to dn.exact="cn=search,ou=support,o=science,dc=nodomain" attrs=userPassword,shadowLastChange
332
+ by anonymous auth
333
+ by dn="cn=admin,dc=nodomain" write
334
+ by * none
335
+ olcAccess: {1}
336
+ to attrs=userPassword,shadowLastChange
337
+ by self write
338
+ by anonymous auth
339
+ by dn="cn=admin,dc=nodomain" write
340
+ by * none
341
+ olcAccess: {2}
342
+ to dn.subtree="ou=user,o=science,dc=nodomain" filter=(|(objectClass=organizationalUnit)(objectClass=inetOrgPerson))
343
+ by dn.exact="cn=search,ou=support,o=science,dc=nodomain" read
344
+ olcAccess: {3}
345
+ to *
346
+ by dn.exact="cn=search,ou=support,o=science,dc=nodomain" none
347
+ olcAccess: {4}
348
+ to dn.base=""
349
+ by * read
350
+ olcAccess: {5}
351
+ to *
352
+ by self write
353
+ by dn="cn=admin,dc=nodomain" write
354
+ by * read
355
+ EOF
356
+ end
357
+
358
+ desc 'enable slapd verbose logging'
359
+ task :logging_on do
360
+ docker_exec_i 'ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///', <<EOF
361
+ dn: cn=config
362
+ changetype: modify
363
+ replace: olcLogLevel
364
+ olcLogLevel: 8 32 64 128 256
365
+ EOF
366
+ end
367
+
368
+ desc 'disable slapd verbose logging'
369
+ task :logging_off do
370
+ docker_exec_i 'ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///', <<EOF
371
+ dn: cn=config
372
+ changetype: modify
373
+ replace: olcLogLevel
374
+ olcLogLevel: 64 256
375
+ EOF
376
+ end
377
+ end
378
+
379
+ # Local Variables:
380
+ # mode: Ruby
381
+ # indent-tabs-mode: nil
382
+ # End: