rom-ldap 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +251 -0
  3. data/CONTRIBUTING.md +18 -0
  4. data/README.md +172 -0
  5. data/TODO.md +33 -0
  6. data/config/responses.yml +328 -0
  7. data/lib/dry/monitor/ldap/colorizers/default.rb +17 -0
  8. data/lib/dry/monitor/ldap/colorizers/rouge.rb +31 -0
  9. data/lib/dry/monitor/ldap/logger.rb +58 -0
  10. data/lib/rom-ldap.rb +1 -0
  11. data/lib/rom/ldap.rb +22 -0
  12. data/lib/rom/ldap/alias.rb +30 -0
  13. data/lib/rom/ldap/associations.rb +6 -0
  14. data/lib/rom/ldap/associations/core.rb +23 -0
  15. data/lib/rom/ldap/associations/many_to_many.rb +18 -0
  16. data/lib/rom/ldap/associations/many_to_one.rb +22 -0
  17. data/lib/rom/ldap/associations/one_to_many.rb +32 -0
  18. data/lib/rom/ldap/associations/one_to_one.rb +19 -0
  19. data/lib/rom/ldap/associations/self_ref.rb +35 -0
  20. data/lib/rom/ldap/attribute.rb +327 -0
  21. data/lib/rom/ldap/client.rb +185 -0
  22. data/lib/rom/ldap/client/authentication.rb +118 -0
  23. data/lib/rom/ldap/client/operations.rb +233 -0
  24. data/lib/rom/ldap/commands.rb +6 -0
  25. data/lib/rom/ldap/commands/create.rb +41 -0
  26. data/lib/rom/ldap/commands/delete.rb +17 -0
  27. data/lib/rom/ldap/commands/update.rb +35 -0
  28. data/lib/rom/ldap/constants.rb +193 -0
  29. data/lib/rom/ldap/dataset.rb +286 -0
  30. data/lib/rom/ldap/dataset/conversion.rb +62 -0
  31. data/lib/rom/ldap/dataset/dsl.rb +299 -0
  32. data/lib/rom/ldap/dataset/persistence.rb +44 -0
  33. data/lib/rom/ldap/directory.rb +126 -0
  34. data/lib/rom/ldap/directory/capabilities.rb +71 -0
  35. data/lib/rom/ldap/directory/entry.rb +200 -0
  36. data/lib/rom/ldap/directory/env.rb +155 -0
  37. data/lib/rom/ldap/directory/operations.rb +282 -0
  38. data/lib/rom/ldap/directory/password.rb +122 -0
  39. data/lib/rom/ldap/directory/root.rb +187 -0
  40. data/lib/rom/ldap/directory/tokenization.rb +66 -0
  41. data/lib/rom/ldap/directory/transactions.rb +31 -0
  42. data/lib/rom/ldap/directory/vendors/active_directory.rb +129 -0
  43. data/lib/rom/ldap/directory/vendors/apache_ds.rb +27 -0
  44. data/lib/rom/ldap/directory/vendors/e_directory.rb +16 -0
  45. data/lib/rom/ldap/directory/vendors/open_directory.rb +12 -0
  46. data/lib/rom/ldap/directory/vendors/open_dj.rb +25 -0
  47. data/lib/rom/ldap/directory/vendors/open_ldap.rb +35 -0
  48. data/lib/rom/ldap/directory/vendors/three_eight_nine.rb +16 -0
  49. data/lib/rom/ldap/directory/vendors/unknown.rb +22 -0
  50. data/lib/rom/ldap/dsl.rb +76 -0
  51. data/lib/rom/ldap/errors.rb +47 -0
  52. data/lib/rom/ldap/expression.rb +77 -0
  53. data/lib/rom/ldap/expression_encoder.rb +174 -0
  54. data/lib/rom/ldap/extensions.rb +50 -0
  55. data/lib/rom/ldap/extensions/active_support_notifications.rb +26 -0
  56. data/lib/rom/ldap/extensions/compatibility.rb +11 -0
  57. data/lib/rom/ldap/extensions/dsml.rb +165 -0
  58. data/lib/rom/ldap/extensions/msgpack.rb +23 -0
  59. data/lib/rom/ldap/extensions/optimised_json.rb +25 -0
  60. data/lib/rom/ldap/extensions/rails_log_subscriber.rb +38 -0
  61. data/lib/rom/ldap/formatter.rb +26 -0
  62. data/lib/rom/ldap/functions.rb +207 -0
  63. data/lib/rom/ldap/gateway.rb +145 -0
  64. data/lib/rom/ldap/ldif.rb +74 -0
  65. data/lib/rom/ldap/ldif/exporter.rb +77 -0
  66. data/lib/rom/ldap/ldif/importer.rb +95 -0
  67. data/lib/rom/ldap/mapper_compiler.rb +19 -0
  68. data/lib/rom/ldap/matchers.rb +69 -0
  69. data/lib/rom/ldap/message_queue.rb +7 -0
  70. data/lib/rom/ldap/oid.rb +101 -0
  71. data/lib/rom/ldap/parsers/abstract_syntax.rb +91 -0
  72. data/lib/rom/ldap/parsers/attribute.rb +290 -0
  73. data/lib/rom/ldap/parsers/filter_syntax.rb +133 -0
  74. data/lib/rom/ldap/pdu.rb +285 -0
  75. data/lib/rom/ldap/plugin/pagination.rb +145 -0
  76. data/lib/rom/ldap/plugins.rb +7 -0
  77. data/lib/rom/ldap/projection_dsl.rb +38 -0
  78. data/lib/rom/ldap/relation.rb +135 -0
  79. data/lib/rom/ldap/relation/exporting.rb +72 -0
  80. data/lib/rom/ldap/relation/reading.rb +461 -0
  81. data/lib/rom/ldap/relation/writing.rb +64 -0
  82. data/lib/rom/ldap/responses.rb +17 -0
  83. data/lib/rom/ldap/restriction_dsl.rb +45 -0
  84. data/lib/rom/ldap/schema.rb +123 -0
  85. data/lib/rom/ldap/schema/attributes_inferrer.rb +59 -0
  86. data/lib/rom/ldap/schema/dsl.rb +13 -0
  87. data/lib/rom/ldap/schema/inferrer.rb +50 -0
  88. data/lib/rom/ldap/schema/type_builder.rb +133 -0
  89. data/lib/rom/ldap/scope.rb +19 -0
  90. data/lib/rom/ldap/search_request.rb +249 -0
  91. data/lib/rom/ldap/socket.rb +210 -0
  92. data/lib/rom/ldap/tasks/ldap.rake +103 -0
  93. data/lib/rom/ldap/tasks/ldif.rake +80 -0
  94. data/lib/rom/ldap/transaction.rb +29 -0
  95. data/lib/rom/ldap/type_map.rb +88 -0
  96. data/lib/rom/ldap/types.rb +158 -0
  97. data/lib/rom/ldap/version.rb +17 -0
  98. data/lib/rom/plugins/relation/ldap/active_directory.rb +182 -0
  99. data/lib/rom/plugins/relation/ldap/auto_restrictions.rb +69 -0
  100. data/lib/rom/plugins/relation/ldap/e_directory.rb +27 -0
  101. data/lib/rom/plugins/relation/ldap/instrumentation.rb +35 -0
  102. data/lib/rouge/lexers/ldap.rb +72 -0
  103. data/lib/rouge/themes/ldap.rb +49 -0
  104. metadata +231 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/parsers/abstract_syntax'
4
+ require 'rom/ldap/parsers/filter_syntax'
5
+
6
+ module ROM
7
+ module LDAP
8
+ class Dataset
9
+
10
+ # Parsing Formats
11
+ #
12
+ # @api public
13
+ module Conversion
14
+ # Extends the class with filter abstraction behavior.
15
+ #
16
+ # @api private
17
+ def self.included(klass)
18
+ klass.class_eval do
19
+ extend Dry::Core::ClassAttributes
20
+
21
+ defines :filter_class
22
+ filter_class Parsers::FilterSyntax
23
+
24
+ defines :ast_class
25
+ ast_class Parsers::AbstractSyntax
26
+ end
27
+ end
28
+
29
+ # Convert the full query to an LDAP filter string
30
+ #
31
+ # @return [String]
32
+ #
33
+ # @api private
34
+ def to_filter
35
+ self.class.ast_class.new(to_ast, directory.attribute_types).call.to_filter
36
+ end
37
+
38
+ # Combine original relation dataset name (LDAP filter string)
39
+ # with search criteria (AST).
40
+ #
41
+ # @return [Array]
42
+ #
43
+ # @api private
44
+ def to_ast
45
+ criteria.empty? ? source_ast : [:con_and, [source_ast, criteria]]
46
+ end
47
+
48
+ private
49
+
50
+ # Convert the relation's source filter string to a query AST.
51
+ #
52
+ # @return [Array]
53
+ #
54
+ # @api private
55
+ def source_ast
56
+ self.class.filter_class.new(name, directory.attribute_types).call
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ class Dataset
6
+
7
+ # AST Query Interface
8
+ #
9
+ # @api private
10
+ module DSL
11
+ # Invert the whole query
12
+ #
13
+ # @return [Dataset]
14
+ def inverse
15
+ with(criteria: [:con_not, criteria])
16
+ end
17
+
18
+ # Equality filter aliased as 'where'.
19
+ #
20
+ # @example
21
+ # relation.where(uid: 'Pietro')
22
+ # relation.where(uid: %w[Pietro Wanda])
23
+ # relation.where(uid: 'Pietro', sn: 'Maximoff')
24
+ #
25
+ # @param args [Hash]
26
+ #
27
+ # @return [Dataset]
28
+ def equal(args)
29
+ chain(*array_dsl(args))
30
+ end
31
+
32
+ # Antonym of 'equal'
33
+ #
34
+ # @example
35
+ # relation.unequal(uid: 'Pietro')
36
+ # relation.unequal(uid: %w[Pietro Wanda])
37
+ #
38
+ # @param args [Hash]
39
+ #
40
+ # @return [Dataset]
41
+ def unequal(args)
42
+ chain(:con_not, array_dsl(args))
43
+ end
44
+
45
+ # Presence filter aliased as 'has'.
46
+ #
47
+ # @example
48
+ # relation.present(:uid)
49
+ # relation.has(:mail)
50
+ #
51
+ # @param attribute [Symbol]
52
+ #
53
+ # @return [Dataset]
54
+ def present(attribute)
55
+ chain(:op_eql, attribute, :wildcard)
56
+ end
57
+ alias_method :has, :present
58
+
59
+ # Absence filter aliased as 'hasnt'. Inverse of 'present'.
60
+ #
61
+ # @example
62
+ # relation.missing(:uid)
63
+ # relation.hasnt(:mail)
64
+ #
65
+ # @param attribute [Symbol]
66
+ #
67
+ # @return [Dataset]
68
+ def missing(attribute)
69
+ chain(:con_not, [:op_eql, attribute, :wildcard])
70
+ end
71
+ alias_method :hasnt, :missing
72
+
73
+ # Greater than filter
74
+ #
75
+ # @param args [Hash]
76
+ #
77
+ # @return [Dataset]
78
+ def gt(args)
79
+ chain(:con_not, [:op_lte, *args.to_a[0]])
80
+ end
81
+ alias_method :above, :gt
82
+
83
+ # Less than filter
84
+ #
85
+ # @param args [Hash]
86
+ #
87
+ # @return [Dataset]
88
+ def lt(args)
89
+ chain(:con_not, [:op_gte, *args.to_a[0]])
90
+ end
91
+ alias_method :below, :lt
92
+
93
+ # Greater than or equal filter
94
+ #
95
+ # @param args [Hash]
96
+ #
97
+ # @return [Dataset]
98
+ def gte(args)
99
+ chain(:op_gte, *args.to_a[0])
100
+ end
101
+
102
+ # Less than or equal filter
103
+ #
104
+ # @param args [Hash]
105
+ #
106
+ # @return [Dataset]
107
+ def lte(args)
108
+ chain(:op_lte, *args.to_a[0])
109
+ end
110
+
111
+ # Starts with filter
112
+ #
113
+ # @param args [Hash]
114
+ #
115
+ # @return [Dataset]
116
+ def begins(args)
117
+ chain(*match_dsl(args, right: WILDCARD))
118
+ end
119
+
120
+ # Ends with filter
121
+ #
122
+ # @param args [Hash]
123
+ #
124
+ # @return [Dataset]
125
+ def ends(args)
126
+ chain(*match_dsl(args, left: WILDCARD))
127
+ end
128
+
129
+ # @param args [Hash]
130
+ #
131
+ # @return [Dataset]
132
+ def contains(args)
133
+ chain(*match_dsl(args, left: WILDCARD, right: WILDCARD))
134
+ end
135
+ alias_method :matches, :contains
136
+
137
+ # negate #contains
138
+ #
139
+ # @param args [Hash]
140
+ #
141
+ # @return [Dataset]
142
+ def excludes(args)
143
+ chain(:con_not, match_dsl(args, left: WILDCARD, right: WILDCARD))
144
+ end
145
+
146
+ # @param args [Range]
147
+ #
148
+ # @return [Dataset]
149
+ def within(args)
150
+ chain(:con_and, cover_dsl(args))
151
+ end
152
+ alias_method :between, :within
153
+
154
+ # negate #outside
155
+ #
156
+ # @param args [Range]
157
+ #
158
+ # @return [Dataset]
159
+ def outside(args)
160
+ chain(:con_not, [:con_and, cover_dsl(args)])
161
+ end
162
+
163
+ # @param args [Hash]
164
+ #
165
+ # @return [Dataset]
166
+ def binary_equal(args)
167
+ chain(:op_bineq, *args.to_a[0])
168
+ end
169
+
170
+ # @param args [Hash]
171
+ #
172
+ # @return [Dataset]
173
+ def approx(args)
174
+ chain(:op_prx, *args.to_a[0])
175
+ end
176
+
177
+ # @param args [Hash]
178
+ #
179
+ # @return [Dataset]
180
+ def bitwise(args)
181
+ chain(:op_ext, *args.to_a[0])
182
+ end
183
+
184
+ private
185
+
186
+ # Update the criteria.
187
+ # If criteria already exist join with AND.
188
+ #
189
+ # @example
190
+ # chain(:op_eql, :uid, "*foo*")
191
+ #
192
+ # @param exprs [Mixed] AST built by QueryDSL
193
+ #
194
+ # @return [Dataset]
195
+ def chain(*exprs)
196
+ if criteria.empty?
197
+ with(criteria: exprs)
198
+ else
199
+ with(criteria: [:con_and, [criteria, exprs]])
200
+ end
201
+ end
202
+
203
+ # Handle multiple criteria with an OR join.
204
+ # @see #chain for AND join.
205
+ #
206
+ # @param args [Hash]
207
+ #
208
+ # @return [Array] AST
209
+ #
210
+ # @example
211
+ # array_dsl(sn: 'Maximoff', gn: %w[Wanda Pietror])
212
+ # =>
213
+ # [ :con_or,
214
+ # [
215
+ # [ :op_eql, :sn, "Maximoff" ],
216
+ # [ :con_or,
217
+ # [
218
+ # [ :op_eql, :gn, "Wanda" ],
219
+ # [ :op_eql, :gn, "Pietror" ]
220
+ # ]
221
+ # ]
222
+ # ]
223
+ # ]
224
+ #
225
+ def array_dsl(args)
226
+ expressions = args.map do |left, right|
227
+ values = Array(right).map { |v| [:op_eql, left, escape(v)] }
228
+ join_dsl(:con_or, values)
229
+ end
230
+ join_dsl(:con_or, expressions)
231
+
232
+ # join_dsl(:con_or, [ match_dsl(args.map(&:to_a)) ])
233
+ # join_dsl(:con_or, args.map { |arg| match_dsl([arg]) })
234
+ end
235
+
236
+ # Wrap criteria with a join operator.
237
+ #
238
+ # @param operator [Symbol] :con_or, :con_and
239
+ #
240
+ # @param ary [Array] [[op, left, right],[op, left, right]]
241
+ def join_dsl(operator, ary)
242
+ ary.size >= 2 ? [operator, ary] : ary.first
243
+ end
244
+
245
+ # Wrap criteria value with extra characters, used for wildcard
246
+ #
247
+ # @param args [Array]
248
+ #
249
+ # @option :left [String] Prepended to value.
250
+ # @option :right [String] Appended to value.
251
+ #
252
+ # @return [Array]
253
+ #
254
+ # @example
255
+ # match_dsl(bar: 'foo', left: '*', right: '*')
256
+ # => [:op_eql, :bar, '*foo*']
257
+ #
258
+ def match_dsl(args, left: EMPTY_STRING, right: EMPTY_STRING)
259
+ expressions = args.map do |att, val|
260
+ values = Array(val).map do |v|
261
+ value = left.to_s + escape(v) + right.to_s
262
+ [:op_eql, att, value]
263
+ end
264
+
265
+ join_dsl(:con_or, values)
266
+ end
267
+ join_dsl(:con_or, expressions)
268
+ end
269
+
270
+ #
271
+ # @param args [Range,Array]
272
+ #
273
+ # @return [Array]
274
+ def cover_dsl(args)
275
+ attribute, range = args.to_a[0]
276
+ lower, *_, upper = range.to_a
277
+
278
+ lower = [:op_gte, attribute, lower]
279
+ upper = [:op_lte, attribute, upper]
280
+
281
+ [lower, upper]
282
+ end
283
+
284
+ # If any of the following special characters appear in the
285
+ # search filter as literals, they must be escaped with a backslash.
286
+ #
287
+ # "(", ")", "\", ,"/" "*", "null"
288
+ #
289
+ # @param value [String, Integer]
290
+ #
291
+ # @return [String]
292
+ def escape(value)
293
+ value.to_s.gsub(ESCAPE_REGEX) { |char| '\\' + ESCAPES[char] }
294
+ end
295
+ end
296
+
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ class Dataset
6
+
7
+ module Persistence
8
+ # Interface to Directory#add
9
+ #
10
+ # @param tuple [Hash]
11
+ #
12
+ # @return [Boolean]
13
+ #
14
+ # @api public
15
+ def add(tuple)
16
+ directory.add(tuple)
17
+ end
18
+
19
+ # Interface to Directory#modify
20
+ #
21
+ # @param tuple [Changeset, Hash] Modification params
22
+ #
23
+ # @return [Array<Directory::Entry, Boolean>]
24
+ #
25
+ # @api public
26
+ def modify(tuple)
27
+ map { |e| directory.modify(e.dn, tuple) }
28
+ end
29
+
30
+ # Interface to Directory#delete
31
+ #
32
+ # @see Directory::Operations#delete
33
+ #
34
+ # @return [Array<Directory::Entry, Boolean>]
35
+ #
36
+ # @api public
37
+ def delete
38
+ map { |e| directory.delete(e.dn) }
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/client'
4
+
5
+ require 'rom/ldap/directory/env'
6
+ require 'rom/ldap/directory/entry'
7
+ require 'rom/ldap/directory/root'
8
+ require 'rom/ldap/directory/capabilities'
9
+ require 'rom/ldap/directory/operations'
10
+ require 'rom/ldap/directory/transactions'
11
+ require 'rom/ldap/directory/tokenization'
12
+
13
+ module ROM
14
+ module LDAP
15
+ # @abstract
16
+ # Builds LDAP directory abstraction over TCP connection instance.
17
+ # Includes vendor specific modules once initialised.
18
+ #
19
+ class Directory
20
+
21
+ attr_accessor :logger
22
+ attr_reader :env
23
+
24
+ # Load vendor specific methods into the instance as singletons
25
+ #
26
+ # @see Gateway#directory
27
+ #
28
+ # @return [ROM::LDAP::Directory]
29
+ #
30
+ def initialize(uri, options)
31
+ @env = ENV.new(uri, options)
32
+ @logger = options.fetch(:logger, Logger.new($stdout))
33
+
34
+ require "rom/ldap/directory/vendors/#{type}"
35
+ extend LDAP.const_get(Inflector.camelize(type))
36
+ end
37
+
38
+ include Root
39
+ include Capabilities
40
+ include Tokenization
41
+ include Operations
42
+ include Transactions
43
+
44
+ # Initial search base.
45
+ #
46
+ # @return [String] Defaults to empty string.
47
+ #
48
+ # @api public
49
+ def base
50
+ env.base
51
+ end
52
+
53
+ # Encapsulates encoding and binding using socket.
54
+ #
55
+ # @return [Client]
56
+ #
57
+ def client
58
+ @client ||= Client.new(env.to_h)
59
+ end
60
+
61
+ # Expected method inside gateway.
62
+ #
63
+ # @return [TrueClass]
64
+ #
65
+ def disconnect
66
+ client.close
67
+ client.closed?
68
+ end
69
+
70
+ # Parsed attributes.
71
+ # Output influenced by LDAP.formatter. Ordered alphabetically.
72
+ #
73
+ # @return [Array<Hash>]
74
+ #
75
+ def attribute_types
76
+ @attribute_types ||= schema_attribute_types.map(&method(:to_attribute)).flatten.freeze
77
+ end
78
+
79
+ # Look up canonical name for a formatted attribute.
80
+ #
81
+ # @see Dataset#order_by and Dataset#select
82
+ #
83
+ # @param attrs [Array<Symbol,String>] formatted attribute names.
84
+ #
85
+ # @return [Array<String>] camelCased canonical LDAP attributes.
86
+ #
87
+ def canonical_attributes(attrs)
88
+ attrs.map do |formatted|
89
+ attribute_by(:name, formatted).fetch(:canonical, formatted.to_s)
90
+ end
91
+ end
92
+
93
+ # Query parsed attribute definition hashes by key.
94
+ #
95
+ # @example
96
+ # directory.attribute_by(:name, :jpeg_photo) => { name: :jpeg_photo, ...}
97
+ #
98
+ # @param key [Symbol]
99
+ # @param value [Mixed]
100
+ #
101
+ # @return [Hash]
102
+ #
103
+ def attribute_by(key, value)
104
+ attribute_types.find { |a| a[key].eql?(value) } || EMPTY_HASH
105
+ end
106
+
107
+ # Hash of attribute names converted => original
108
+ #
109
+ # @example { formatted_name: 'canonicalName' }
110
+ #
111
+ # @return [Hash]
112
+ #
113
+ def key_map
114
+ attribute_types.map { |a| a.values_at(:name, :canonical) }.sort.to_h
115
+ end
116
+
117
+ # @return [String]
118
+ #
119
+ # @api public
120
+ def inspect
121
+ "#<#{self.class} uri='#{env.connection}' vendor='#{vendor_name}' version='#{vendor_version}' />"
122
+ end
123
+
124
+ end
125
+ end
126
+ end