lom 0.11.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/mapper.rb +29 -18
- data/lib/lom/version.rb +1 -1
- data/lom.gemspec +1 -0
- metadata +16 -2
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/mapper.rb
CHANGED
|
@@ -114,8 +114,12 @@ module Mapper
|
|
|
114
114
|
# * the LH constant in this scope or parent scope
|
|
115
115
|
# * the one provided by LOM.lh
|
|
116
116
|
#
|
|
117
|
+
# If the handler is a callable, the handler built by it for the
|
|
118
|
+
# current thread is returned (see LOM.lh=).
|
|
119
|
+
#
|
|
117
120
|
def lh
|
|
118
|
-
@__ldap_lh ||
|
|
121
|
+
LOM.materialize_lh(@__ldap_lh ||
|
|
122
|
+
(const_defined?(:LH) && const_get(:LH)) || LOM.lh)
|
|
119
123
|
end
|
|
120
124
|
|
|
121
125
|
# Set the LDAP handler to use
|
|
@@ -275,23 +279,30 @@ module Mapper
|
|
|
275
279
|
end
|
|
276
280
|
|
|
277
281
|
# Perform search
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
}
|
|
295
306
|
}
|
|
296
307
|
|
|
297
308
|
# Return self
|
data/lib/lom/version.rb
CHANGED
data/lom.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
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'
|
|
54
68
|
description: |2+
|
|
55
69
|
|
|
56
70
|
Creation of mapping between ldap entry and ruby object.
|
|
@@ -92,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
92
106
|
- !ruby/object:Gem::Version
|
|
93
107
|
version: '0'
|
|
94
108
|
requirements: []
|
|
95
|
-
rubygems_version:
|
|
109
|
+
rubygems_version: 4.0.21
|
|
96
110
|
specification_version: 4
|
|
97
111
|
summary: LDAP Object Mapper
|
|
98
112
|
test_files: []
|