rims-passwd-ldap 0.0.1

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