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,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