lom 0.10.0 → 0.12.0
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 +4 -4
- data/README.md +43 -0
- data/lib/lom/handler.rb +134 -4
- data/lib/lom/ldap/converters.rb +13 -13
- data/lib/lom/ldap/extensions.rb +1 -0
- data/lib/lom/mapper.rb +85 -20
- data/lib/lom/version.rb +1 -1
- data/lom.gemspec +1 -0
- metadata +17 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7080950578c9a8cdcff8a1a6e527d2c53a2981fb05f32099e114ac3a73f9221
|
|
4
|
+
data.tar.gz: 49c5a2a4072773cf445f6ba30ee60acb59fb8745a78f927fda4eb987e60d4efe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc64dfa9fcaa777ae4e9dd4dd65e0b3527f2bfa1482389aa83714c1d6bad346a4806a174886c2fc74c1ab3a235bbbbf548d13a978650bc6a1a580eb65d778f17
|
|
7
|
+
data.tar.gz: e79faf8dd9af3b388ccf6b7ee67a084832ad1eadab13bc955838492f6feb9cccbcc341f4dd526bb92fe145fcaffc08e9c1d85b12abe02370fd9c61b8aff0ab29
|
data/README.md
CHANGED
|
@@ -156,3 +156,46 @@ User.locked(true).manager('jdoe').list
|
|
|
156
156
|
# Return list of users (as User instance) without managers
|
|
157
157
|
User.manager(false).all
|
|
158
158
|
~~~
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
Threaded servers
|
|
162
|
+
================
|
|
163
|
+
|
|
164
|
+
A `Net::LDAP` handler is not thread safe: each operation stores its
|
|
165
|
+
outcome in the handler itself, so two threads sharing a handler can
|
|
166
|
+
read each other's result. When LOM is used from several threads
|
|
167
|
+
(Puma, background jobs, ...), give it a callable instead of a
|
|
168
|
+
handler: it is called once per thread (or fiber), and the handler it
|
|
169
|
+
builds is reused by that thread.
|
|
170
|
+
|
|
171
|
+
~~~ruby
|
|
172
|
+
LH = -> { Net::LDAP.connect('ldap://127.0.0.1')
|
|
173
|
+
.tap { it.auth 'uid=root,ou=Admins,dc=example,dc=com',
|
|
174
|
+
'foobar' } }
|
|
175
|
+
~~~
|
|
176
|
+
|
|
177
|
+
Without an opened connection, `Net::LDAP` connects and binds for each
|
|
178
|
+
operation. `LOM.open` keeps a single connection for all the operations
|
|
179
|
+
of its block; nested calls reuse it, and if the connection or bind
|
|
180
|
+
fails the block still runs with one connection per operation. It only
|
|
181
|
+
opens when the handler comes from a callable, as an opened connection
|
|
182
|
+
must not be shared between threads.
|
|
183
|
+
|
|
184
|
+
~~~ruby
|
|
185
|
+
LOM.open { User.locked(true).all }
|
|
186
|
+
~~~
|
|
187
|
+
|
|
188
|
+
In a Rack application, a middleware gives one connection per request:
|
|
189
|
+
|
|
190
|
+
~~~ruby
|
|
191
|
+
class LDAPConnection
|
|
192
|
+
def initialize(app) = @app = app
|
|
193
|
+
def call(env) = LOM.open { @app.call(env) }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
use LDAPConnection
|
|
197
|
+
~~~
|
|
198
|
+
|
|
199
|
+
`LOM.open` only opens the handler of `LOM.lh`; a mapper given its own
|
|
200
|
+
handler (`lh=` or its own `LH` constant) still uses one connection per
|
|
201
|
+
operation.
|
data/lib/lom/handler.rb
CHANGED
|
@@ -4,6 +4,19 @@ class LOM
|
|
|
4
4
|
using LDAP::Extensions
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
# Set the LDAP handler to use
|
|
8
|
+
#
|
|
9
|
+
# It can be a Net::LDAP handler, or a callable (proc, lambda, ...)
|
|
10
|
+
# building one. A Net::LDAP handler is not thread safe, so a
|
|
11
|
+
# callable is required as soon as LOM is used from several threads:
|
|
12
|
+
# it will be called once per thread (or fiber), and the resulting
|
|
13
|
+
# handler reused by that thread.
|
|
14
|
+
#
|
|
15
|
+
# ~~~
|
|
16
|
+
# LOM.lh = -> { Net::LDAP.connect('ldap://127.0.0.1')
|
|
17
|
+
# .tap { it.auth(dn, password) } }
|
|
18
|
+
# ~~~
|
|
19
|
+
#
|
|
7
20
|
def self.lh=(lh)
|
|
8
21
|
@lh = lh
|
|
9
22
|
end
|
|
@@ -16,11 +29,131 @@ class LOM
|
|
|
16
29
|
# * the LH constant in this scope or parent scope
|
|
17
30
|
# * the one defined in $lh
|
|
18
31
|
#
|
|
32
|
+
# If the handler is a callable, the handler built by it for the
|
|
33
|
+
# current thread is returned.
|
|
34
|
+
#
|
|
19
35
|
def self.lh
|
|
20
|
-
|
|
36
|
+
materialize_lh(lh_source)
|
|
21
37
|
end
|
|
22
38
|
|
|
39
|
+
# Get the LDAP handler source (handler or callable), see lh
|
|
40
|
+
#
|
|
41
|
+
# @api private
|
|
42
|
+
#
|
|
43
|
+
def self.lh_source
|
|
44
|
+
@lh || (const_defined?(:LH) && const_get(:LH)) || $lh
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Resolve a handler source to a usable LDAP handler.
|
|
48
|
+
#
|
|
49
|
+
# A callable is called once per thread (or fiber) and its result
|
|
50
|
+
# cached for that thread, anything else is returned unchanged.
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
#
|
|
54
|
+
def self.materialize_lh(src)
|
|
55
|
+
return src unless src.respond_to?(:call)
|
|
56
|
+
(Thread.current[:__lom_lh] ||= {}.compare_by_identity)[src] ||=
|
|
57
|
+
src.call
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Run the block with a single LDAP connection opened and bound,
|
|
61
|
+
# which is reused by all the LDAP operations of the block,
|
|
62
|
+
# instead of one connection per operation.
|
|
63
|
+
#
|
|
64
|
+
# This is only done if the handler is a callable (see lh=), as an
|
|
65
|
+
# opened connection must not be shared between threads, otherwise
|
|
66
|
+
# the block is simply run.
|
|
67
|
+
#
|
|
68
|
+
# If the connection or bind fails, the block is still run, falling
|
|
69
|
+
# back to one connection per operation.
|
|
70
|
+
#
|
|
71
|
+
# Nested calls reuse the already opened connection.
|
|
72
|
+
#
|
|
73
|
+
# @return the block value
|
|
74
|
+
#
|
|
75
|
+
def self.open
|
|
76
|
+
src = lh_source
|
|
77
|
+
return yield unless src.respond_to?(:call)
|
|
78
|
+
|
|
79
|
+
lh = materialize_lh(src)
|
|
80
|
+
opened = (Thread.current[:__lom_opened] ||= {}.compare_by_identity)
|
|
81
|
+
return yield if opened[lh]
|
|
82
|
+
|
|
83
|
+
entered = false
|
|
84
|
+
result = nil
|
|
85
|
+
begin
|
|
86
|
+
lh.open do
|
|
87
|
+
code = lh.get_operation_result.code
|
|
88
|
+
next unless code == Net::LDAP::ResultCodeSuccess
|
|
89
|
+
opened[lh] = { depth: 0, dirty: false }
|
|
90
|
+
entered = true
|
|
91
|
+
begin
|
|
92
|
+
result = yield
|
|
93
|
+
ensure
|
|
94
|
+
opened.delete(lh)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
rescue Net::LDAP::Error, SystemCallError, IOError
|
|
98
|
+
raise if entered
|
|
99
|
+
end
|
|
100
|
+
entered ? result : yield
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Run the block performing a search on the handler.
|
|
104
|
+
#
|
|
105
|
+
# If the handler connection was opened by LOM.open and the search
|
|
106
|
+
# is left before completion (break, exception, ...), the unread
|
|
107
|
+
# search results would be kept until the end of LOM.open, so the
|
|
108
|
+
# connection is replaced by a new one, once no other search is
|
|
109
|
+
# using it.
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
112
|
+
#
|
|
113
|
+
def self.searching(lh)
|
|
114
|
+
state = Thread.current[:__lom_opened]&.[](lh)
|
|
115
|
+
return yield if state.nil?
|
|
116
|
+
|
|
117
|
+
state[:depth] += 1
|
|
118
|
+
done = false
|
|
119
|
+
begin
|
|
120
|
+
yield.tap { done = true }
|
|
121
|
+
ensure
|
|
122
|
+
state[:depth] -= 1
|
|
123
|
+
state[:dirty] ||= !done
|
|
124
|
+
reopen(lh, state) if state[:dirty] && state[:depth].zero?
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Replace the connection opened by LOM.open with a new one.
|
|
129
|
+
# If it can't be opened and bound, operations fall back to
|
|
130
|
+
# one connection per operation.
|
|
131
|
+
#
|
|
132
|
+
# @note relies on Net::LDAP internals (@open_connection, @auth,
|
|
133
|
+
# #new_connection) as used by Net::LDAP#open
|
|
134
|
+
#
|
|
135
|
+
# @api private
|
|
136
|
+
#
|
|
137
|
+
def self.reopen(lh, state)
|
|
138
|
+
state[:dirty] = false
|
|
139
|
+
begin
|
|
140
|
+
lh.instance_variable_get(:@open_connection)&.close
|
|
141
|
+
rescue StandardError
|
|
142
|
+
end
|
|
143
|
+
lh.instance_variable_set(:@open_connection, nil)
|
|
23
144
|
|
|
145
|
+
conn = lh.send(:new_connection)
|
|
146
|
+
res = conn.bind(lh.instance_variable_get(:@auth))
|
|
147
|
+
if res.result_code == Net::LDAP::ResultCodeSuccess
|
|
148
|
+
lh.instance_variable_set(:@open_connection, conn)
|
|
149
|
+
else
|
|
150
|
+
conn.close
|
|
151
|
+
end
|
|
152
|
+
rescue Net::LDAP::Error, SystemCallError, IOError
|
|
153
|
+
conn&.close rescue nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
24
157
|
# extend Forwardable
|
|
25
158
|
#
|
|
26
159
|
# def self.connect(*args)
|
|
@@ -38,6 +171,3 @@ class LOM
|
|
|
38
171
|
# def_delegator :@lh, :delete
|
|
39
172
|
|
|
40
173
|
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
data/lib/lom/ldap/converters.rb
CHANGED
|
@@ -10,25 +10,25 @@ module Converters
|
|
|
10
10
|
#
|
|
11
11
|
# Integer
|
|
12
12
|
#
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
refine Integer do
|
|
15
15
|
def to_ldap
|
|
16
16
|
self.to_s
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
refine Integer.singleton_class do
|
|
21
21
|
def from_ldap(v)
|
|
22
22
|
Integer(v)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
|
|
28
28
|
#
|
|
29
29
|
# String
|
|
30
30
|
#
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
refine String do
|
|
33
33
|
def to_ldap
|
|
34
34
|
self
|
|
@@ -41,15 +41,15 @@ module Converters
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
|
|
46
46
|
#
|
|
47
47
|
# Date / Time
|
|
48
48
|
#
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
refine Date do
|
|
51
51
|
def to_ldap
|
|
52
|
-
self.
|
|
52
|
+
self.strftime("%Y%m%d%H%M%SZ")
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -74,12 +74,12 @@ module Converters
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
|
|
79
79
|
#
|
|
80
80
|
# Boolean
|
|
81
81
|
#
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
refine TrueClass do
|
|
84
84
|
def to_ldap
|
|
85
85
|
'TRUE'
|
|
@@ -96,20 +96,20 @@ module Converters
|
|
|
96
96
|
def to_ldap
|
|
97
97
|
'FALSE'
|
|
98
98
|
end
|
|
99
|
-
end
|
|
99
|
+
end
|
|
100
|
+
|
|
100
101
|
|
|
101
102
|
|
|
102
|
-
|
|
103
103
|
#
|
|
104
104
|
# Array / Set
|
|
105
105
|
#
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
refine Set do
|
|
108
108
|
def to_ldap
|
|
109
109
|
self.to_a.to_ldap
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
refine Array do
|
|
114
114
|
def to_ldap
|
|
115
115
|
self.map { |val|
|
data/lib/lom/ldap/extensions.rb
CHANGED
data/lib/lom/mapper.rb
CHANGED
|
@@ -56,6 +56,27 @@ module InstanceMethods
|
|
|
56
56
|
lh.add(dn: dn, attributes: attrs)
|
|
57
57
|
}
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Delete object from ldap
|
|
62
|
+
#
|
|
63
|
+
# @return [true, false]
|
|
64
|
+
#
|
|
65
|
+
def delete!
|
|
66
|
+
model = self.class
|
|
67
|
+
attrs = instance_exec(self, &model.ldap_to)
|
|
68
|
+
.transform_values {|v|
|
|
69
|
+
# Don't use Array(), not what you think on
|
|
70
|
+
# some classes such as Time
|
|
71
|
+
v = [ ] if v.nil?
|
|
72
|
+
v = [ v ] unless v.is_a?(Array)
|
|
73
|
+
v.to_ldap
|
|
74
|
+
}
|
|
75
|
+
id, _ = Array(attrs[model.ldap_prefix])
|
|
76
|
+
raise MappingError, 'prefix for dn has multiple values' if _
|
|
77
|
+
|
|
78
|
+
model.delete!(id)
|
|
79
|
+
end
|
|
59
80
|
end
|
|
60
81
|
end
|
|
61
82
|
|
|
@@ -93,8 +114,12 @@ module Mapper
|
|
|
93
114
|
# * the LH constant in this scope or parent scope
|
|
94
115
|
# * the one provided by LOM.lh
|
|
95
116
|
#
|
|
117
|
+
# If the handler is a callable, the handler built by it for the
|
|
118
|
+
# current thread is returned (see LOM.lh=).
|
|
119
|
+
#
|
|
96
120
|
def lh
|
|
97
|
-
@__ldap_lh ||
|
|
121
|
+
LOM.materialize_lh(@__ldap_lh ||
|
|
122
|
+
(const_defined?(:LH) && const_get(:LH)) || LOM.lh)
|
|
98
123
|
end
|
|
99
124
|
|
|
100
125
|
# Set the LDAP handler to use
|
|
@@ -254,23 +279,30 @@ module Mapper
|
|
|
254
279
|
end
|
|
255
280
|
|
|
256
281
|
# Perform search
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
282
|
+
# Note: the search must not be left early (with break), as it
|
|
283
|
+
# would leave unread results on a connection opened
|
|
284
|
+
# by LOM.open; leaving it from the caller block is
|
|
285
|
+
# handled by LOM.searching
|
|
286
|
+
h = lh
|
|
287
|
+
LOM.searching(h) {
|
|
288
|
+
h.search(:base => _ldap_branch,
|
|
289
|
+
:filter => filter,
|
|
290
|
+
:attributes => attributes,
|
|
291
|
+
:scope => _ldap_scope) {|entry|
|
|
292
|
+
|
|
293
|
+
if rawfilter && !rawfilter.call(entry)
|
|
294
|
+
next
|
|
295
|
+
elsif paged.nil?
|
|
296
|
+
yield(converter.(entry))
|
|
297
|
+
elsif skip > 0
|
|
298
|
+
skip -= 1
|
|
299
|
+
elsif count <= 0
|
|
300
|
+
next
|
|
301
|
+
else
|
|
302
|
+
count -= 1
|
|
303
|
+
yield(converter.(entry))
|
|
304
|
+
end
|
|
305
|
+
}
|
|
274
306
|
}
|
|
275
307
|
|
|
276
308
|
# Return self
|
|
@@ -296,8 +328,16 @@ module Mapper
|
|
|
296
328
|
def list(&rawfilter)
|
|
297
329
|
each(:id, rawfilter: rawfilter).to_a
|
|
298
330
|
end
|
|
299
|
-
|
|
300
|
-
|
|
331
|
+
|
|
332
|
+
# Fetch the requested entry.
|
|
333
|
+
#
|
|
334
|
+
# @param param [String] entry name
|
|
335
|
+
#
|
|
336
|
+
# @raise [LOM::EntryNotFound] if entry not found
|
|
337
|
+
#
|
|
338
|
+
# @return [Object]
|
|
339
|
+
#
|
|
340
|
+
def fetch(name)
|
|
301
341
|
dn = ldap_dn_from_id(name)
|
|
302
342
|
attrs = _ldap_attrs
|
|
303
343
|
entry = lh.get(:dn => dn, :attributes => attrs)
|
|
@@ -310,8 +350,33 @@ module Mapper
|
|
|
310
350
|
lh.delete(:dn => dn)
|
|
311
351
|
end
|
|
312
352
|
|
|
353
|
+
# Get the requested entry.
|
|
354
|
+
# Same as #fetch but return nil if not found
|
|
355
|
+
#
|
|
356
|
+
# @param param [String] entry name
|
|
357
|
+
#
|
|
358
|
+
# @return [nil] entry not found
|
|
359
|
+
# @return [Object]
|
|
360
|
+
#
|
|
361
|
+
def get(name)
|
|
362
|
+
fetch(name)
|
|
363
|
+
rescue LOM::EntryNotFound
|
|
364
|
+
nil
|
|
365
|
+
end
|
|
366
|
+
|
|
313
367
|
alias [] get
|
|
314
368
|
|
|
369
|
+
# Test existence of entry
|
|
370
|
+
#
|
|
371
|
+
# @param param [String] entry name
|
|
372
|
+
#
|
|
373
|
+
# @return [Boolean]
|
|
374
|
+
#
|
|
375
|
+
def exists?(name)
|
|
376
|
+
dn = ldap_dn_from_id(name)
|
|
377
|
+
lh.get(:dn => dn, :return_result => false)
|
|
378
|
+
end
|
|
379
|
+
|
|
315
380
|
private
|
|
316
381
|
|
|
317
382
|
def _ldap_branch
|
data/lib/lom/version.rb
CHANGED
data/lom.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lom
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stéphane D'Alu
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: net-ldap
|
|
@@ -52,6 +51,20 @@ dependencies:
|
|
|
52
51
|
- - "~>"
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '13'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5'
|
|
55
68
|
description: |2+
|
|
56
69
|
|
|
57
70
|
Creation of mapping between ldap entry and ruby object.
|
|
@@ -79,7 +92,6 @@ homepage: https://github.com/sdalu/lom
|
|
|
79
92
|
licenses:
|
|
80
93
|
- MIT
|
|
81
94
|
metadata: {}
|
|
82
|
-
post_install_message:
|
|
83
95
|
rdoc_options: []
|
|
84
96
|
require_paths:
|
|
85
97
|
- lib
|
|
@@ -94,8 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
106
|
- !ruby/object:Gem::Version
|
|
95
107
|
version: '0'
|
|
96
108
|
requirements: []
|
|
97
|
-
rubygems_version:
|
|
98
|
-
signing_key:
|
|
109
|
+
rubygems_version: 4.0.21
|
|
99
110
|
specification_version: 4
|
|
100
111
|
summary: LDAP Object Mapper
|
|
101
112
|
test_files: []
|