lom 0.9.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 383b6cdf0a55888e40558ca5b24f46a7f5e5d033f27fc873e4625c42b385c571
4
- data.tar.gz: cef1eef2f7ee9b2b39118e9f54b2d15f1ba5d35518ed04e98cb09a047cbbb34e
3
+ metadata.gz: b3a1b6204446324ec92c63443efc8c9578db7628aa4544f42c435a1605ec05aa
4
+ data.tar.gz: 7fa61741e1c85db00a5ab168d2814afcc0a21ba0ba2a914649f7e8ef6f6df815
5
5
  SHA512:
6
- metadata.gz: dd9eab41714ae145477e2756300b0041dd2d0a12f90485a3997e95266bbb121a33e8a3ed3660d8fd14e7372fd0cfa0fca6da6f800f61599a1c995da221b67b4f
7
- data.tar.gz: f435226cb86088cfa48db588b105396b79fc0172eec4a967168b507dffb1039f201f6d7261d371edf63883c7fe519ca0d6167c649259f282a7d38e592ee929e1
6
+ metadata.gz: 468294c000324a795a2a3cb67de0436c492e4d08caffea7d14bbc47486b81640fb2c352df233771aefa1cc136e71a1dbcc98efe24c49c7b79d6952f824e1787d
7
+ data.tar.gz: 4add52468b4bb244a215dfa2ac32244417f3b282d615c19f2dcc30cc97601bd9c678180047da6de881bd9952f6423f3d5ac743d0d575dd80079f9d6ddb3e5432
data/README.md CHANGED
@@ -104,7 +104,13 @@ class User < Dry::Struct
104
104
  }
105
105
  end
106
106
 
107
+ ldap_list :query, ->(str) do
108
+ Filtered.match(:uid, str) |
109
+ Filtered.match(:cn, str) |
110
+ Filtered.match(:givenName, str) | Filtered.match(:sn, str)
111
+ end
107
112
 
113
+
108
114
  #
109
115
  # Object structure
110
116
  #
data/lib/lom/filtered.rb CHANGED
@@ -13,10 +13,19 @@ class Filtered
13
13
  NONE = Object.new.freeze
14
14
  ANY = Object.new.freeze
15
15
 
16
- def initialize(src, filter = nil, paged: nil)
17
- @src = src
18
- @filter = filter
19
- @paged = paged
16
+ class NoSource < Error
17
+ end
18
+
19
+ def initialize(filter = nil, src: nil, paged: nil)
20
+ if filter.is_a?(Filtered)
21
+ @filter = filter.filter
22
+ @src = src || filter.src
23
+ @paged = paged || filter.paged
24
+ else
25
+ @filter = filter
26
+ @src = src
27
+ @paged = paged
28
+ end
20
29
  end
21
30
  attr_reader :src, :filter, :paged
22
31
 
@@ -30,7 +39,7 @@ class Filtered
30
39
  _operator_2('&', o)
31
40
  end
32
41
 
33
- # Take the negation of this fileter
42
+ # Take the negation of this filter
34
43
  def ~@
35
44
  _operator_1('!')
36
45
  end
@@ -51,24 +60,26 @@ class Filtered
51
60
  end
52
61
 
53
62
  # Iterate over matching data
54
- def each(*args, &block)
55
- @src.each(*args, filter: @filter, paged: self.paged, &block)
63
+ def each(*args, rawfilter: nil, &block)
64
+ raise NoSource if @src.nil?
65
+ @src.each(*args, filter: @filter, rawfilter: rawfilter,
66
+ paged: self.paged, &block)
56
67
  end
57
68
 
58
69
  # Retrieve matching data as a list of object
59
70
  #
60
71
  # @return [Array<Object>]
61
72
  #
62
- def all
63
- each(:object).to_a
73
+ def all(&rawfilter)
74
+ each(:object, rawfilter: rawfilter).to_a
64
75
  end
65
76
 
66
77
  # Retrieve matching data as a list of id
67
78
  #
68
79
  # @return [Array<String>]
69
80
  #
70
- def list
71
- each(:id).to_a
81
+ def list(&rawfilter)
82
+ each(:id, rawfilter: rawfilter).to_a
72
83
  end
73
84
 
74
85
  # Escape (and convert) a value for correct processing.
@@ -89,20 +100,20 @@ class Filtered
89
100
 
90
101
  # Test if an attribute exists
91
102
  def self.exists(attr, predicate: true)
92
- case predicate
93
- when true, nil then "(#{attr}=*)"
94
- when false, :none then "(!(#{attr}=*))"
95
- else raise ArgumentError
96
- end
103
+ self.new(case predicate
104
+ when true, nil then "(#{attr}=*)"
105
+ when false, :none then "(!(#{attr}=*))"
106
+ else raise ArgumentError
107
+ end)
97
108
  end
98
109
 
99
110
  # Test if an attribute is of the specified value
100
111
  def self.is(attr, val, predicate: true)
101
- case predicate
102
- when true, nil then "(#{attr}=#{escape(val)})"
103
- when false then "(!(#{attr}=#{escape(val)}))"
104
- else raise ArgumentError
105
- end
112
+ self.new(case predicate
113
+ when true, nil then "(#{attr}=#{escape(val)})"
114
+ when false then "(!(#{attr}=#{escape(val)}))"
115
+ else raise ArgumentError
116
+ end)
106
117
  end
107
118
 
108
119
  # Test if an attribute has the specified value.
@@ -110,11 +121,20 @@ class Filtered
110
121
  def self.has(attr, val)
111
122
  val = yield(val) if block_given?
112
123
 
113
- case val
114
- when ANY then "(#{attr}=*)"
115
- when NONE then "(!(#{attr}=*))"
116
- else "(#{attr}=#{escape(val)})"
117
- end
124
+ self.new(case val
125
+ when ANY then "(#{attr}=*)"
126
+ when NONE then "(!(#{attr}=*))"
127
+ else "(#{attr}=#{escape(val)})"
128
+ end)
129
+ end
130
+
131
+ # Test if an attribute match the specified value
132
+ def self.match(attr, val, predicate: true)
133
+ self.new(case predicate
134
+ when true, nil then "(#{attr}=*#{escape(val)}*)"
135
+ when false then "(!(#{attr}=*#{escape(val)}*))"
136
+ else raise ArgumentError
137
+ end)
118
138
  end
119
139
 
120
140
  # Test if an attribute as a time before the specified timestamp
@@ -122,7 +142,7 @@ class Filtered
122
142
  def self.before(attr, ts, predicate: true)
123
143
  ts = Date.today + ts if ts.kind_of?(Integer)
124
144
  ts = LOM.to_ldap_time(ts)
125
- "(#{attr}<=#{ts})".then {|f| predicate ? f : "(!#{f})" }
145
+ self.new("(#{attr}<=#{ts})".then {|f| predicate ? f : "(!#{f})" })
126
146
  end
127
147
 
128
148
  # Test if an attribute as a time after the specified timestamp
@@ -130,40 +150,40 @@ class Filtered
130
150
  def self.after(attr, ts, predicate: true)
131
151
  ts = Date.today - ts if ts.kind_of?(Integer)
132
152
  ts = LOM.to_ldap_time(ts)
133
- "(#{attr}>=#{ts})".then {|f| predicate ? f : "(!#{f})" }
153
+ self.new("(#{attr}>=#{ts})".then {|f| predicate ? f : "(!#{f})" })
134
154
  end
135
155
 
136
156
  private
137
157
 
138
158
  # Operation with 2 elements
139
159
  def _operator_2(op, o)
140
- if @src != o.src
160
+ if !@src.nil? && !o.src.nil? && @src != o.src
141
161
  raise ArgumentError, 'filter defined with different sources'
142
162
  end
143
163
  _filter = if !@filter.nil? && !o.filter.nil?
144
164
  then Net::LDAP.filter(op, @filter, o.filter)
145
165
  else @filter || o.filter
146
166
  end
147
- Filtered.new(@src, _filter, paged: o.paged || self.paged)
167
+ Filtered.new(_filter, src: @src || o.src, paged: o.paged || self.paged)
148
168
  end
149
169
 
150
170
  # Operation with 1 element
151
171
  def _operator_1(op)
152
- Filtered.new(@src, Net::LDAP.filter(op, @filter),
172
+ Filtered.new(Net::LDAP.filter(op, @filter), src: @src,
153
173
  paged: self.paged)
154
174
  end
155
175
 
156
176
  # Check if an ldap_list has been defined with that name
157
177
  def respond_to_missing?(method_name, include_private = false)
178
+ return super if @src.nil?
158
179
  @src.ldap_listing.include?(method_name) || super
159
180
  end
160
181
 
161
182
  # Call the ldap_list defined with that name
162
183
  def method_missing(method_name, *args, &block)
163
- if @src.ldap_listing.include?(method_name)
164
- self & @src.send(method_name, *args, &block)
165
- else
166
- super
184
+ if @src&.ldap_listing.include?(method_name)
185
+ then self & @src.send(method_name, *args, &block)
186
+ else super
167
187
  end
168
188
  end
169
189
 
@@ -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.to_time.to_ldap
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|
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ require 'uri'
3
4
  require 'net/ldap'
4
5
  require 'net/ldap/dn'
5
6
 
@@ -188,7 +189,7 @@ module Extensions
188
189
  if LOM.debug.include?(:dry)
189
190
  return true
190
191
  end
191
- return true if op.empty? # That's a no-op
192
+ return true if ops.empty? # That's a no-op
192
193
  modify(:dn => dn, :operations => ops) # Apply modifications
193
194
  end
194
195
  end
data/lib/lom/mapper.rb CHANGED
@@ -37,7 +37,8 @@ module InstanceMethods
37
37
  # @return [true, false]
38
38
  #
39
39
  def save!
40
- attrs = instance_exec(self, &self.class._ldap_to)
40
+ model = self.class
41
+ attrs = instance_exec(self, &model.ldap_to)
41
42
  .transform_values {|v|
42
43
  # Don't use Array(), not what you think on
43
44
  # some classes such as Time
@@ -45,9 +46,9 @@ module InstanceMethods
45
46
  v = [ v ] unless v.is_a?(Array)
46
47
  v.to_ldap
47
48
  }
48
- id, _ = Array(attrs[self.class._ldap_prefix])
49
+ id, _ = Array(attrs[model.ldap_prefix])
49
50
  raise MappingError, 'prefix for dn has multiple values' if _
50
- dn = self.class.ldap_dn_from_id(id)
51
+ dn = model.ldap_dn_from_id(id)
51
52
 
52
53
  lh.update(dn: dn, attributes: attrs).then {|res|
53
54
  break res unless res.nil?
@@ -55,6 +56,27 @@ module InstanceMethods
55
56
  lh.add(dn: dn, attributes: attrs)
56
57
  }
57
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
58
80
  end
59
81
  end
60
82
 
@@ -101,7 +123,10 @@ module Mapper
101
123
  @__ldap_lh = lh
102
124
  end
103
125
 
104
-
126
+ # Return the list of defined list (using ldap_list).
127
+ #
128
+ # @return [Array<Symbol>] list of defined ldap list
129
+ #
105
130
  def ldap_listing
106
131
  @__ldap_list
107
132
  end
@@ -118,16 +143,18 @@ module Mapper
118
143
  @__ldap_list << name
119
144
  define_singleton_method(name) do |*args|
120
145
  filter = body.call(*args)
121
- LOM::Filtered.new(self, filter)
146
+ LOM::Filtered.new(filter, src: self)
122
147
  end
123
148
  end
124
149
 
125
150
 
126
- def ldap_branch(v)
151
+ def ldap_branch(v = nil)
152
+ return _ldap_branch if v.nil?
127
153
  @__ldap_branch = v
128
154
  end
129
155
 
130
- def ldap_prefix(v)
156
+ def ldap_prefix(v = nil)
157
+ return _ldap_prefix if v.nil?
131
158
  @__ldap_prefix = v
132
159
  end
133
160
 
@@ -154,6 +181,8 @@ module Mapper
154
181
 
155
182
  # @note block will be executed in the mapped object instance
156
183
  def ldap_to(p=nil, &b)
184
+ return _ldap_to if p.nil? && b.nil?
185
+
157
186
  if (! p.nil? ^ b.nil?) || (p && !p.kind_of?(Proc))
158
187
  raise ArgumentError,
159
188
  'one and only one of proc/lamba/block need to be defined'
@@ -162,7 +191,15 @@ module Mapper
162
191
  end
163
192
 
164
193
 
165
-
194
+ # Convert a dn to it's corresponding id the current mapping.
195
+ #
196
+ # @raise [Error] dn belongs to this mapping (it is in the mapping
197
+ # branch), but is malformed (not a direct child, or
198
+ # wrong prefix)
199
+ #
200
+ # @return [String] id
201
+ # @return [nil] dn is not from this mapping
202
+ #
166
203
  def ldap_dn_to_id(dn)
167
204
  prefix = _ldap_prefix.to_s
168
205
  branch = _ldap_branch
@@ -171,8 +208,8 @@ module Mapper
171
208
  case prefix
172
209
  when String, Symbol
173
210
  k, v, _ = sub.to_a
174
- raise ArgumentError, "not a direct child" if _
175
- raise ArgumentError, "wrong prefix" if k.casecmp(prefix) != 0
211
+ raise Error, "not a direct child" if _
212
+ raise Error, "wrong prefix" if k.casecmp(prefix) != 0
176
213
  v
177
214
  end
178
215
  end
@@ -190,24 +227,45 @@ module Mapper
190
227
  self.new(*args)
191
228
  end
192
229
 
193
-
194
- def each(type = :object, filter: nil, paged: nil)
230
+
231
+ # Iterate over matching data.
232
+ #
233
+ # @note If using `rawfilter`, no optimization will be performed
234
+ # aned the ldap attributes will be retrieved,
235
+ # even if desired type is :id
236
+ #
237
+ # @param type [:object, :id] return object or id
238
+ # @param filter [String] extra ldap search filter
239
+ # @param rawfilter [Proc] filter on ldap entry
240
+ # @param paged [Array<Integer,Integer>] pagination information
241
+ #
242
+ # @yieldparam obj_or_id [Object, String] ldap converted element according to type
243
+ #
244
+ # @return [Enumerator] if no block given
245
+ # @return [self] if block given
246
+ #
247
+ def each(type = :object, filter: nil, rawfilter: nil, paged: nil)
195
248
  # Create Enumerator if no block given
196
249
  unless block_given?
197
- return enum_for(:each, type, filter: filter, paged: paged)
250
+ return enum_for(:each, type,
251
+ filter: filter, rawfilter: rawfilter, paged: paged)
198
252
  end
199
253
 
200
254
  # Merging filters
201
- filters = [ filter, _ldap_filter ].compact
202
- filter = filters.size == 2 ? "(&#{filters.join})" : filters.first
255
+ filter = Net::LDAP.filter('&', *[ filter, _ldap_filter ].compact)
203
256
 
204
257
  # Define attributes/converter according to selected type
205
258
  attributes, converter =
206
259
  case type
207
- when :id then [ :dn, ->(e) { ldap_dn_to_id(e.dn) } ]
208
- when :object then [ _ldap_attrs, ->(e) { _ldap_to_obj(e) } ]
260
+ when :id then [ rawfilter ? _ldap_attrs : :dn,
261
+ ->(e) { ldap_dn_to_id(e.dn) }
262
+ ]
263
+ when :object then [ _ldap_attrs,
264
+ ->(e) { _ldap_to_obj(e) }
265
+ ]
209
266
  else raise ArgumentError, 'type must be either :object or :id'
210
267
  end
268
+
211
269
 
212
270
  # Paginate
213
271
  # XXX: pagination is emulated, should be avoided
@@ -222,7 +280,9 @@ module Mapper
222
280
  :attributes => attributes,
223
281
  :scope => _ldap_scope) {|entry|
224
282
 
225
- if paged.nil?
283
+ if rawfilter && !rawfilter.call(entry)
284
+ next
285
+ elsif paged.nil?
226
286
  yield(converter.(entry))
227
287
  elsif skip > 0
228
288
  skip -= 1
@@ -233,21 +293,40 @@ module Mapper
233
293
  yield(converter.(entry))
234
294
  end
235
295
  }
296
+
297
+ # Return self
298
+ self
236
299
  end
237
300
 
238
301
  def paginate(page, page_size)
239
- LOM::Filtered.new(self, paged: [ page, page_size ])
302
+ LOM::Filtered.new(src: self, paged: [ page, page_size ])
240
303
  end
241
304
 
242
- def all
243
- each(:object).to_a
305
+ # Retrieve matching data as a list of object
306
+ #
307
+ # @return [Array<Object>]
308
+ #
309
+ def all(&rawfilter)
310
+ each(:object, rawfilter: rawfilter).to_a
244
311
  end
245
312
 
246
- def list
247
- each(:id).to_a
313
+ # Retrieve matching data as a list of id
314
+ #
315
+ # @return [Array<String>]
316
+ #
317
+ def list(&rawfilter)
318
+ each(:id, rawfilter: rawfilter).to_a
248
319
  end
249
-
250
- def get(name)
320
+
321
+ # Fetch the requested entry.
322
+ #
323
+ # @param param [String] entry name
324
+ #
325
+ # @raise [LOM::EntryNotFound] if entry not found
326
+ #
327
+ # @return [Object]
328
+ #
329
+ def fetch(name)
251
330
  dn = ldap_dn_from_id(name)
252
331
  attrs = _ldap_attrs
253
332
  entry = lh.get(:dn => dn, :attributes => attrs)
@@ -260,8 +339,33 @@ module Mapper
260
339
  lh.delete(:dn => dn)
261
340
  end
262
341
 
342
+ # Get the requested entry.
343
+ # Same as #fetch but return nil if not found
344
+ #
345
+ # @param param [String] entry name
346
+ #
347
+ # @return [nil] entry not found
348
+ # @return [Object]
349
+ #
350
+ def get(name)
351
+ fetch(name)
352
+ rescue LOM::EntryNotFound
353
+ nil
354
+ end
355
+
263
356
  alias [] get
264
357
 
358
+ # Test existence of entry
359
+ #
360
+ # @param param [String] entry name
361
+ #
362
+ # @return [Boolean]
363
+ #
364
+ def exists?(name)
365
+ dn = ldap_dn_from_id(name)
366
+ lh.get(:dn => dn, :return_result => false)
367
+ end
368
+
265
369
  private
266
370
 
267
371
  def _ldap_branch
data/lib/lom/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class LOM
2
- VERSION = '0.9.0'
2
+ VERSION = '0.11.0'
3
3
  end
data/lom.gemspec CHANGED
@@ -8,15 +8,13 @@ Gem::Specification.new do |s|
8
8
  s.summary = "LDAP Object Mapper"
9
9
  s.description = <<~EOF
10
10
 
11
- Ease processing of parameters in Sinatra framework.
12
- Integrates well with dry-types, sequel, ...
11
+ Creation of mapping between ldap entry and ruby object.
12
+ Allowing easy retrieval of information, building of search filter,
13
+ and updating ldap.
13
14
 
14
- Example:
15
- want! :user, Dry::Types::String, User
16
- want? :expired, Dry::Types::Params::Bool.default(true)
17
15
  EOF
18
16
 
19
- s.homepage = 'https://gitlab.com/sdalu/lom'
17
+ s.homepage = 'https://github.com/sdalu/lom'
20
18
  s.license = 'MIT'
21
19
 
22
20
  s.authors = [ "Stéphane D'Alu" ]
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.9.0
4
+ version: 0.11.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: 2021-05-26 00:00:00.000000000 Z
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,14 +51,12 @@ dependencies:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
53
  version: '13'
55
- description: |2
54
+ description: |2+
56
55
 
57
- Ease processing of parameters in Sinatra framework.
58
- Integrates well with dry-types, sequel, ...
56
+ Creation of mapping between ldap entry and ruby object.
57
+ Allowing easy retrieval of information, building of search filter,
58
+ and updating ldap.
59
59
 
60
- Example:
61
- want! :user, Dry::Types::String, User
62
- want? :expired, Dry::Types::Params::Bool.default(true)
63
60
  email:
64
61
  - stephane.dalu@insa-lyon.fr
65
62
  executables: []
@@ -77,11 +74,10 @@ files:
77
74
  - lib/lom/mapper.rb
78
75
  - lib/lom/version.rb
79
76
  - lom.gemspec
80
- homepage: https://gitlab.com/sdalu/lom
77
+ homepage: https://github.com/sdalu/lom
81
78
  licenses:
82
79
  - MIT
83
80
  metadata: {}
84
- post_install_message:
85
81
  rdoc_options: []
86
82
  require_paths:
87
83
  - lib
@@ -96,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
92
  - !ruby/object:Gem::Version
97
93
  version: '0'
98
94
  requirements: []
99
- rubygems_version: 3.0.8
100
- signing_key:
95
+ rubygems_version: 3.6.9
101
96
  specification_version: 4
102
97
  summary: LDAP Object Mapper
103
98
  test_files: []
99
+ ...