rom-ldap 0.2.2

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.
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,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module Parsers
8
+ #
9
+ # Parses a filter into an AST
10
+ #
11
+ # @api private
12
+ class FilterSyntax
13
+
14
+ extend Initializer
15
+
16
+ param :filter, type: Types::Filter
17
+
18
+ param :attributes, type: Types.Array(Types::Hash)
19
+
20
+ attr_accessor :result
21
+
22
+ def call
23
+ if open_statement
24
+ if joined_statement
25
+ join_type = encode_constructor
26
+
27
+ branches = []
28
+
29
+ while (branch = call)
30
+ branches << branch
31
+ end
32
+
33
+ self.result = [join_type, branches]
34
+
35
+ elsif negated_statement
36
+ negator = encode_constructor
37
+ self.result = [negator, call]
38
+
39
+ else
40
+ skip_whitespace
41
+ scan_attribute
42
+ attribute = encode_attribute
43
+
44
+ skip_whitespace
45
+ scan_operator
46
+ op = encode_operator
47
+
48
+ skip_whitespace
49
+ scan_value
50
+ value = encode_value
51
+
52
+ self.result = [op, attribute, value]
53
+ end
54
+
55
+ close_statement
56
+
57
+ result
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # @return [StringScanner]
64
+ #
65
+ def scanner
66
+ @scanner ||= StringScanner.new(filter)
67
+ end
68
+
69
+ def skip_whitespace
70
+ scanner.scan(/\s*/)
71
+ end
72
+
73
+ def close_statement
74
+ scanner.scan(/\s*\)\s*/)
75
+ end
76
+
77
+ def open_statement
78
+ scanner.scan(/\s*\(\s*/)
79
+ end
80
+
81
+ # @return [String, NilClass] "!"
82
+ #
83
+ def negated_statement
84
+ scanner.scan(/\s*\!\s*/)
85
+ end
86
+
87
+ # @return [String, NilClass] "&" or "|"
88
+ #
89
+ def joined_statement
90
+ scanner.scan(CONSTRUCTOR_REGEX)
91
+ end
92
+
93
+ def scan_value
94
+ scanner.scan(VAL_REGEX)
95
+ end
96
+
97
+ def scan_operator
98
+ scanner.scan(OPERATOR_REGEX)
99
+ end
100
+
101
+ def scan_attribute
102
+ scanner.scan(/[-\w:.]*[\w]/)
103
+ end
104
+
105
+ # @return [String,Symbol] formatted
106
+ #
107
+ def encode_attribute
108
+ attr = attributes.find { |a| a[:canonical].eql?(scanner.matched) }
109
+ attr ? attr[:name] : scanner.matched
110
+ end
111
+
112
+ # @return [Mixed]
113
+ #
114
+ def encode_value
115
+ Functions[:identify_value].call(scanner.matched)
116
+ end
117
+
118
+ # @return [Symbol]
119
+ #
120
+ def encode_constructor
121
+ CONSTRUCTORS.invert[scanner.matched]
122
+ end
123
+
124
+ # @return [Symbol]
125
+ #
126
+ def encode_operator
127
+ OPERATORS.invert[scanner.matched]
128
+ end
129
+
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ require 'dry/core/class_attributes'
5
+ require 'rom/initializer'
6
+
7
+ module ROM
8
+ module LDAP
9
+ # @see PDU
10
+ #
11
+ # @return [Array<Symbol>]
12
+ #
13
+ SUCCESS_CODES = %i[
14
+ success
15
+ time_limit_exceeded
16
+ size_limit_exceeded
17
+ compare_true
18
+ compare_false
19
+ referral
20
+ sasl_bind_in_progress
21
+ ].freeze
22
+
23
+ # LDAP Message Protocol Data Unit (PDU)
24
+ #
25
+ # @api private
26
+ class PDU
27
+
28
+ extend Initializer
29
+
30
+ param :message_id, proc(&:to_i), reader: true, type: Types::Strict::Integer
31
+
32
+ # @!attribute [r] tag
33
+ # @return [Array]
34
+ param :tag, reader: :private, type: Types::Strict::Array
35
+
36
+ param :ctrls, reader: :private, type: Types::Strict::Array, default: -> { EMPTY_ARRAY }
37
+
38
+ # @return [Integer]
39
+ #
40
+ def app_tag
41
+ tag.ber_identifier & 0x1f
42
+ end
43
+
44
+ # @return [Integer]
45
+ #
46
+ def result_code
47
+ tag[0]
48
+ end
49
+
50
+ #
51
+ # @return [String]
52
+ #
53
+ def matched_dn
54
+ tag[1]
55
+ end
56
+
57
+ # @example
58
+ # "Matchingrule is required for sorting by the attribute cn"
59
+ #
60
+ # @return [String]
61
+ #
62
+ def advice
63
+ tag[2]
64
+ end
65
+
66
+ def search_referrals
67
+ return unless search_referral?
68
+
69
+ tag[3] || EMPTY_ARRAY
70
+ end
71
+
72
+ def result_server_sasl_creds
73
+ return unless bind_result? && gteq4?
74
+
75
+ tag[3]
76
+ end
77
+
78
+ # @return [Array] => [version, username, password]
79
+ #
80
+ #
81
+ def bind_result
82
+ return unless bind_result?
83
+ raise ResponseTypeInvalidError, 'Invalid bind_result' unless gteq_3?
84
+
85
+ tag
86
+ end
87
+
88
+ #
89
+ # @return [OpenStruct] => :version, :name, :authentication
90
+ #
91
+ def bind_parameters
92
+ return unless bind_request?
93
+
94
+ s = ::OpenStruct.new
95
+ s.version, s.name, s.authentication = tag
96
+ s
97
+ end
98
+
99
+ #
100
+ # @return [OpenStruct, NilClass] => :version, :name, :authentication
101
+ #
102
+ def search_parameters
103
+ return unless search_request?
104
+
105
+ s = ::OpenStruct.new
106
+ s.base_object,
107
+ s.scope,
108
+ s.deref_aliases,
109
+ s.size_limit,
110
+ s.time_limit,
111
+ s.types_only,
112
+ s.filter,
113
+ s.attributes = tag
114
+ s
115
+ end
116
+
117
+ # Message
118
+ #
119
+ # @example => "No attribute with the name false exists in the server's schema"
120
+ #
121
+ # @return [String, NilClass]
122
+ #
123
+ def extended_response
124
+ raise ResponseTypeInvalidError, 'Invalid extended_response' unless gteq_3?
125
+
126
+ tag[3] if extended_response?
127
+ end
128
+
129
+ # ["dn", [ entry... ]]
130
+ #
131
+ # @return [Array, NilClass]
132
+ #
133
+ def search_entry
134
+ raise ResponseTypeInvalidError, 'Invalid search_entry' unless gteq_2?
135
+
136
+ tag if search_result?
137
+ end
138
+
139
+ # RFC-2251, an LDAP 'control' is a sequence of tuples, each consisting of
140
+ # - an OID
141
+ # - a boolean criticality flag defaulting FALSE
142
+ # - and an optional Octet String
143
+ #
144
+ # If only two fields are given, the second one may be either criticality
145
+ # or data, since criticality has a default value. RFC-2696 is a good example.
146
+ #
147
+ # @see Connection::Read#search
148
+ #
149
+ # @return [Array<OpenStruct>] => :oid, :criticality, :value
150
+ #
151
+ def result_controls
152
+ ctrls.map do |control|
153
+ oid, level, value = control
154
+ value, level = level, false if level&.is_a?(String)
155
+ ::OpenStruct.new(oid: oid, criticality: level, value: value)
156
+ end
157
+ end
158
+
159
+ # logging ======================= #
160
+
161
+ # First item from responses.yaml
162
+ #
163
+ # @return [String]
164
+ #
165
+ def message
166
+ detailed_response[0]
167
+ end
168
+
169
+ # Grep for error message
170
+ #
171
+ # @return [String, FalseClass]
172
+ #
173
+ def error_message
174
+ tag[3][/comment: (.*), data/, 1] if tag[3]
175
+ end
176
+
177
+ # @return [String]
178
+ #
179
+ def info
180
+ detailed_response[1]
181
+ end
182
+
183
+ # @return [String]
184
+ #
185
+ def flag
186
+ detailed_response[2]
187
+ end
188
+
189
+ # predicates ======================= #
190
+
191
+ # @return [Boolean]
192
+ #
193
+ def add_response?
194
+ pdu_type == :add_response
195
+ end
196
+
197
+ # @see https://tools.ietf.org/html/rfc4511#section-4.2.1
198
+ #
199
+ # @return [Boolean]
200
+ #
201
+ def bind_request?
202
+ pdu_type == :bind_request
203
+ end
204
+
205
+ # A successful operation is indicated by a BindResponse with a resultCode set to success.
206
+ #
207
+ # @see https://tools.ietf.org/html/rfc4511#section-4.2.2
208
+ #
209
+ # @return [Boolean]
210
+ #
211
+ def bind_result?
212
+ pdu_type.eql?(:bind_result)
213
+ end
214
+
215
+ # @return [Boolean]
216
+ #
217
+ def search_request?
218
+ pdu_type.eql?(:search_request)
219
+ end
220
+
221
+ # @return [Boolean]
222
+ #
223
+ def search_result?
224
+ pdu_type.eql?(:search_returned_data)
225
+ end
226
+
227
+ # @return [Boolean]
228
+ #
229
+ def search_referral?
230
+ pdu_type == :search_result_referral
231
+ end
232
+
233
+ # @return [Boolean]
234
+ #
235
+ def extended_response?
236
+ pdu_type == :extended_response
237
+ end
238
+
239
+ # @return [Boolean]
240
+ #
241
+ def success?
242
+ SUCCESS_CODES.include?(result_code_symbol)
243
+ end
244
+
245
+ # @return [Boolean]
246
+ #
247
+ def failure?
248
+ !success?
249
+ end
250
+
251
+ # conversion ======================= #
252
+
253
+ # @return [Symbol]
254
+ #
255
+ def pdu_type
256
+ BER.fetch(:response, app_tag) || raise(ResponseTypeInvalidError, "Unknown pdu_type: #{app_tag}")
257
+ end
258
+
259
+ # @return [Symbol]
260
+ #
261
+ def result_code_symbol
262
+ BER.fetch(:result, result_code)
263
+ end
264
+
265
+ private
266
+
267
+ def detailed_response
268
+ RESPONSES[result_code_symbol]
269
+ end
270
+
271
+ # @return [Boolean]
272
+ #
273
+ def gteq_2?
274
+ tag.length >= 2
275
+ end
276
+
277
+ # @return [Boolean]
278
+ #
279
+ def gteq_3?
280
+ tag.length >= 3
281
+ end
282
+
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/initializer'
4
+ require 'dry/equalizer'
5
+
6
+ module ROM
7
+ module LDAP
8
+ module Plugin
9
+ # Pagination plugin for Relations
10
+ #
11
+ # @api public
12
+ module Pagination
13
+ # Pager object provides the underlying pagination API for relations
14
+ #
15
+ # @api public
16
+ class Pager
17
+
18
+ extend Initializer
19
+
20
+ # @!parse include Dry::Equalizer
21
+ include Dry::Equalizer(:dataset, :options)
22
+
23
+ # @!attribute [r] dataset
24
+ # @return [LDAP::Dataset] Relation's dataset
25
+ param :dataset
26
+
27
+ # @!attribute [r] current_page
28
+ # @return [Integer] Current page number
29
+ option :current_page, default: -> { 1 }
30
+
31
+ # @!attribute [r] per_page
32
+ # @return [Integer] Current per-page number
33
+ option :per_page
34
+
35
+ # Return next page number
36
+ #
37
+ # @example
38
+ # users.page(2).pager.next_page
39
+ # # => 3
40
+ #
41
+ # @return [Integer]
42
+ #
43
+ # @api public
44
+ def next_page
45
+ num = current_page + 1
46
+ num if total_pages >= num
47
+ end
48
+
49
+ # Return previous page number
50
+ #
51
+ # @example
52
+ # users.page(2).pager.prev_page
53
+ # # => 1
54
+ #
55
+ # @return [Integer]
56
+ #
57
+ # @api public
58
+ def prev_page
59
+ num = current_page - 1
60
+ num if num.positive?
61
+ end
62
+
63
+ # Return total number of tuples
64
+ #
65
+ # @return [Integer]
66
+ #
67
+ # @api public
68
+ def total
69
+ dataset.total
70
+ end
71
+
72
+ # Return total number of pages
73
+ #
74
+ # @return [Integer]
75
+ #
76
+ # @api public
77
+ def total_pages
78
+ (total / per_page.to_f).ceil
79
+ end
80
+
81
+ # @api private
82
+ def at(dataset, current_page, per_page = self.per_page)
83
+ current_page = current_page.to_i
84
+ per_page = per_page.to_i
85
+ offset = (current_page - 1) * per_page
86
+
87
+ self.class.new(
88
+ dataset.with(offset: offset, limit: per_page),
89
+ current_page: current_page, per_page: per_page
90
+ )
91
+ end
92
+
93
+ alias_method :limit_value, :per_page
94
+
95
+ end
96
+
97
+ # @api private
98
+ def self.included(klass)
99
+ super
100
+
101
+ klass.class_eval do
102
+ defines :per_page
103
+
104
+ option :pager, default: -> {
105
+ Pager.new(dataset, per_page: self.class.per_page)
106
+ }
107
+ end
108
+ end
109
+
110
+ # Paginate a relation
111
+ #
112
+ # @example
113
+ # users.page(1)
114
+ # users.pager # => info about pagination
115
+ #
116
+ # @return [Relation]
117
+ #
118
+ # @api public
119
+ def page(num)
120
+ next_pager = pager.at(dataset, num)
121
+ new(next_pager.dataset, pager: next_pager)
122
+ end
123
+
124
+ # Set limit for pagination
125
+ #
126
+ # @example
127
+ # users.per_page(10).page(2)
128
+ #
129
+ # @return [Relation]
130
+ #
131
+ # @api public
132
+ def per_page(num)
133
+ next_pager = pager.at(dataset, pager.current_page, num)
134
+ new(next_pager.dataset, pager: next_pager)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ ROM.plugins do
142
+ adapter :ldap do
143
+ register :pagination, ROM::LDAP::Plugin::Pagination, type: :relation
144
+ end
145
+ end