powerhome-activeldap 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +6 -0
  3. data/COPYING +340 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +59 -0
  6. data/README.textile +140 -0
  7. data/TODO +32 -0
  8. data/benchmark/README.md +64 -0
  9. data/benchmark/bench-backend.rb +247 -0
  10. data/benchmark/bench-instantiate.rb +98 -0
  11. data/benchmark/config.yaml.sample +5 -0
  12. data/doc/text/development.textile +54 -0
  13. data/doc/text/news.textile +811 -0
  14. data/doc/text/rails.textile +144 -0
  15. data/doc/text/tutorial.textile +1010 -0
  16. data/examples/config.yaml.example +5 -0
  17. data/examples/example.der +0 -0
  18. data/examples/example.jpg +0 -0
  19. data/examples/groupadd +41 -0
  20. data/examples/groupdel +35 -0
  21. data/examples/groupls +49 -0
  22. data/examples/groupmod +42 -0
  23. data/examples/lpasswd +55 -0
  24. data/examples/objects/group.rb +13 -0
  25. data/examples/objects/ou.rb +4 -0
  26. data/examples/objects/user.rb +20 -0
  27. data/examples/ouadd +38 -0
  28. data/examples/useradd +45 -0
  29. data/examples/useradd-binary +53 -0
  30. data/examples/userdel +34 -0
  31. data/examples/userls +50 -0
  32. data/examples/usermod +42 -0
  33. data/examples/usermod-binary-add +50 -0
  34. data/examples/usermod-binary-add-time +54 -0
  35. data/examples/usermod-binary-del +48 -0
  36. data/examples/usermod-lang-add +43 -0
  37. data/lib/active_ldap.rb +85 -0
  38. data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
  39. data/lib/active_ldap/acts/tree.rb +78 -0
  40. data/lib/active_ldap/adapter/base.rb +707 -0
  41. data/lib/active_ldap/adapter/jndi.rb +184 -0
  42. data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
  43. data/lib/active_ldap/adapter/ldap.rb +290 -0
  44. data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
  45. data/lib/active_ldap/adapter/net_ldap.rb +309 -0
  46. data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
  47. data/lib/active_ldap/association/belongs_to.rb +47 -0
  48. data/lib/active_ldap/association/belongs_to_many.rb +58 -0
  49. data/lib/active_ldap/association/children.rb +21 -0
  50. data/lib/active_ldap/association/collection.rb +105 -0
  51. data/lib/active_ldap/association/has_many.rb +31 -0
  52. data/lib/active_ldap/association/has_many_utils.rb +44 -0
  53. data/lib/active_ldap/association/has_many_wrap.rb +75 -0
  54. data/lib/active_ldap/association/proxy.rb +107 -0
  55. data/lib/active_ldap/associations.rb +205 -0
  56. data/lib/active_ldap/attribute_methods.rb +23 -0
  57. data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
  58. data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
  59. data/lib/active_ldap/attribute_methods/query.rb +31 -0
  60. data/lib/active_ldap/attribute_methods/read.rb +44 -0
  61. data/lib/active_ldap/attribute_methods/write.rb +38 -0
  62. data/lib/active_ldap/attributes.rb +176 -0
  63. data/lib/active_ldap/base.rb +1410 -0
  64. data/lib/active_ldap/callbacks.rb +71 -0
  65. data/lib/active_ldap/command.rb +49 -0
  66. data/lib/active_ldap/compatible.rb +44 -0
  67. data/lib/active_ldap/configuration.rb +147 -0
  68. data/lib/active_ldap/connection.rb +299 -0
  69. data/lib/active_ldap/distinguished_name.rb +291 -0
  70. data/lib/active_ldap/entry_attribute.rb +78 -0
  71. data/lib/active_ldap/escape.rb +12 -0
  72. data/lib/active_ldap/get_text.rb +20 -0
  73. data/lib/active_ldap/get_text/parser.rb +161 -0
  74. data/lib/active_ldap/helper.rb +92 -0
  75. data/lib/active_ldap/human_readable.rb +133 -0
  76. data/lib/active_ldap/ldap_error.rb +74 -0
  77. data/lib/active_ldap/ldif.rb +930 -0
  78. data/lib/active_ldap/log_subscriber.rb +50 -0
  79. data/lib/active_ldap/object_class.rb +95 -0
  80. data/lib/active_ldap/operations.rb +624 -0
  81. data/lib/active_ldap/persistence.rb +100 -0
  82. data/lib/active_ldap/populate.rb +53 -0
  83. data/lib/active_ldap/railtie.rb +43 -0
  84. data/lib/active_ldap/railties/controller_runtime.rb +48 -0
  85. data/lib/active_ldap/schema.rb +701 -0
  86. data/lib/active_ldap/schema/syntaxes.rb +422 -0
  87. data/lib/active_ldap/timeout.rb +75 -0
  88. data/lib/active_ldap/timeout_stub.rb +17 -0
  89. data/lib/active_ldap/user_password.rb +99 -0
  90. data/lib/active_ldap/validations.rb +200 -0
  91. data/lib/active_ldap/version.rb +3 -0
  92. data/lib/active_ldap/xml.rb +139 -0
  93. data/lib/rails/generators/active_ldap/model/USAGE +18 -0
  94. data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
  95. data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
  96. data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
  97. data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
  98. data/po/en/active-ldap.po +4029 -0
  99. data/po/ja/active-ldap.po +4060 -0
  100. data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
  101. data/test/al-test-utils.rb +428 -0
  102. data/test/command.rb +111 -0
  103. data/test/config.yaml.sample +6 -0
  104. data/test/fixtures/lower_case_object_class_schema.rb +802 -0
  105. data/test/run-test.rb +34 -0
  106. data/test/test_acts_as_tree.rb +60 -0
  107. data/test/test_adapter.rb +121 -0
  108. data/test/test_associations.rb +701 -0
  109. data/test/test_attributes.rb +117 -0
  110. data/test/test_base.rb +1214 -0
  111. data/test/test_base_per_instance.rb +61 -0
  112. data/test/test_bind.rb +62 -0
  113. data/test/test_callback.rb +31 -0
  114. data/test/test_configuration.rb +40 -0
  115. data/test/test_connection.rb +82 -0
  116. data/test/test_connection_per_class.rb +112 -0
  117. data/test/test_connection_per_dn.rb +112 -0
  118. data/test/test_dirty.rb +98 -0
  119. data/test/test_dn.rb +172 -0
  120. data/test/test_find.rb +176 -0
  121. data/test/test_groupadd.rb +50 -0
  122. data/test/test_groupdel.rb +46 -0
  123. data/test/test_groupls.rb +107 -0
  124. data/test/test_groupmod.rb +51 -0
  125. data/test/test_ldif.rb +1890 -0
  126. data/test/test_load.rb +133 -0
  127. data/test/test_lpasswd.rb +75 -0
  128. data/test/test_object_class.rb +74 -0
  129. data/test/test_persistence.rb +131 -0
  130. data/test/test_reflection.rb +175 -0
  131. data/test/test_schema.rb +559 -0
  132. data/test/test_syntax.rb +444 -0
  133. data/test/test_user.rb +217 -0
  134. data/test/test_user_password.rb +108 -0
  135. data/test/test_useradd-binary.rb +62 -0
  136. data/test/test_useradd.rb +57 -0
  137. data/test/test_userdel.rb +48 -0
  138. data/test/test_userls.rb +91 -0
  139. data/test/test_usermod-binary-add-time.rb +65 -0
  140. data/test/test_usermod-binary-add.rb +64 -0
  141. data/test/test_usermod-binary-del.rb +66 -0
  142. data/test/test_usermod-lang-add.rb +59 -0
  143. data/test/test_usermod.rb +58 -0
  144. data/test/test_validation.rb +274 -0
  145. metadata +379 -0
@@ -0,0 +1,707 @@
1
+ require 'benchmark'
2
+
3
+ require 'active_ldap/schema'
4
+ require 'active_ldap/entry_attribute'
5
+ require 'active_ldap/ldap_error'
6
+
7
+ module ActiveLdap
8
+ module Adapter
9
+ class Base
10
+ include GetTextSupport
11
+
12
+ VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :timeout,
13
+ :retry_on_timeout, :retry_limit,
14
+ :retry_wait, :bind_dn, :password,
15
+ :password_block, :try_sasl,
16
+ :sasl_mechanisms, :sasl_quiet,
17
+ :allow_anonymous, :store_password,
18
+ :scope, :sasl_options]
19
+
20
+ @@row_even = true
21
+
22
+ attr_reader :runtime
23
+ def initialize(configuration={})
24
+ @runtime = 0
25
+ @connection = nil
26
+ @disconnected = false
27
+ @bound = false
28
+ @bind_tried = false
29
+ @entry_attributes = {}
30
+ @configuration = configuration.dup
31
+ @logger = @configuration.delete(:logger)
32
+ @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
33
+ VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
34
+ instance_variable_set("@#{name}", configuration[name])
35
+ end
36
+ @instrumenter = ActiveSupport::Notifications.instrumenter
37
+ end
38
+
39
+ def reset_runtime
40
+ runtime, @runtime = @runtime, 0
41
+ runtime
42
+ end
43
+
44
+ def connect(options={})
45
+ host = options[:host] || @host
46
+ method = options[:method] || @method || :plain
47
+ port = options[:port] || @port || ensure_port(method)
48
+ method = ensure_method(method)
49
+ @disconnected = false
50
+ @bound = false
51
+ @bind_tried = false
52
+ @connection, @uri, @with_start_tls = yield(host, port, method)
53
+ prepare_connection(options)
54
+ bind(options)
55
+ end
56
+
57
+ def disconnect!(options={})
58
+ unbind(options)
59
+ @connection = @uri = @with_start_tls = nil
60
+ @disconnected = true
61
+ end
62
+
63
+ def rebind(options={})
64
+ unbind(options) if bound?
65
+ connect(options)
66
+ end
67
+
68
+ def bind(options={})
69
+ @bind_tried = true
70
+
71
+ bind_dn = ensure_dn_string(options[:bind_dn] || @bind_dn)
72
+ try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
73
+ if options.has_key?(:allow_anonymous)
74
+ allow_anonymous = options[:allow_anonymous]
75
+ else
76
+ allow_anonymous = @allow_anonymous
77
+ end
78
+ options = options.merge(:allow_anonymous => allow_anonymous)
79
+
80
+ # Rough bind loop:
81
+ # Attempt 1: SASL if available
82
+ # Attempt 2: SIMPLE with credentials if password block
83
+ # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
84
+ if try_sasl and sasl_bind(bind_dn, options)
85
+ @logger.info {_('Bound to %s by SASL as %s') % [target, bind_dn]}
86
+ elsif simple_bind(bind_dn, options)
87
+ @logger.info {_('Bound to %s by simple as %s') % [target, bind_dn]}
88
+ elsif allow_anonymous and bind_as_anonymous(options)
89
+ @logger.info {_('Bound to %s as anonymous') % target}
90
+ else
91
+ message = yield if block_given?
92
+ message ||= _('All authentication methods for %s exhausted.') % target
93
+ raise AuthenticationError, message
94
+ end
95
+
96
+ @bound = true
97
+ @bound
98
+ end
99
+
100
+ def unbind(options={})
101
+ yield if @connection and (@bind_tried or bound?)
102
+ @bind_tried = @bound = false
103
+ end
104
+
105
+ def bind_as_anonymous(options={})
106
+ yield
107
+ end
108
+
109
+ def connecting?
110
+ !@connection.nil? and !@disconnected
111
+ end
112
+
113
+ def bound?
114
+ connecting? and @bound
115
+ end
116
+
117
+ def schema(options={})
118
+ @schema ||= operation(options) do
119
+ base = options[:base]
120
+ attrs = options[:attributes]
121
+
122
+ attrs ||= [
123
+ 'objectClasses',
124
+ 'attributeTypes',
125
+ 'matchingRules',
126
+ 'matchingRuleUse',
127
+ 'dITStructureRules',
128
+ 'dITContentRules',
129
+ 'nameForms',
130
+ 'ldapSyntaxes',
131
+ #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER.
132
+ ]
133
+ base ||= root_dse_values('subschemaSubentry', options)[0]
134
+ base ||= 'cn=schema'
135
+ schema = nil
136
+ search(:base => base,
137
+ :scope => :base,
138
+ :filter => '(objectClass=subschema)',
139
+ :attributes => attrs,
140
+ :limit => 1) do |dn, attributes|
141
+ schema = Schema.new(attributes)
142
+ end
143
+ schema || Schema.new([])
144
+ end
145
+ end
146
+
147
+ def naming_contexts
148
+ root_dse_values('namingContexts')
149
+ end
150
+
151
+ def entry_attribute(object_classes)
152
+ @entry_attributes[object_classes.uniq.sort] ||=
153
+ EntryAttribute.new(schema, object_classes)
154
+ end
155
+
156
+ def search(options={})
157
+ filter = parse_filter(options[:filter]) || 'objectClass=*'
158
+ attrs = options[:attributes] || []
159
+ scope = ensure_scope(options[:scope] || @scope)
160
+ base = options[:base]
161
+ limit = options[:limit] || 0
162
+ limit = nil if limit <= 0
163
+
164
+ attrs = attrs.to_a # just in case
165
+ base = ensure_dn_string(base)
166
+ begin
167
+ operation(options) do
168
+ yield(base, scope, filter, attrs, limit)
169
+ end
170
+ rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax
171
+ # Do nothing on failure
172
+ @logger.info do
173
+ args = [$!.class, $!.message, filter, attrs.inspect]
174
+ _("Ignore error %s(%s): filter %s: attributes: %s") % args
175
+ end
176
+ end
177
+ end
178
+
179
+ def delete(targets, options={})
180
+ targets = [targets] unless targets.is_a?(Array)
181
+ return if targets.empty?
182
+ begin
183
+ operation(options) do
184
+ targets.each do |target|
185
+ target = ensure_dn_string(target)
186
+ begin
187
+ yield(target)
188
+ rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess
189
+ raise OperationNotPermitted, _("%s: %s") % [$!.message, target]
190
+ end
191
+ end
192
+ end
193
+ rescue LdapError::NoSuchObject
194
+ raise EntryNotFound, _("No such entry: %s") % target
195
+ end
196
+ end
197
+
198
+ def add(dn, entries, options={})
199
+ dn = ensure_dn_string(dn)
200
+ begin
201
+ operation(options) do
202
+ yield(dn, entries)
203
+ end
204
+ rescue LdapError::NoSuchObject
205
+ raise EntryNotFound, _("No such entry: %s") % dn
206
+ rescue LdapError::InvalidDnSyntax
207
+ raise DistinguishedNameInvalid.new(dn)
208
+ rescue LdapError::AlreadyExists
209
+ raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn]
210
+ rescue LdapError::StrongAuthRequired
211
+ raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn]
212
+ rescue LdapError::ObjectClassViolation
213
+ raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
214
+ rescue LdapError::UnwillingToPerform
215
+ raise OperationNotPermitted, _("%s: %s") % [$!.message, dn]
216
+ end
217
+ end
218
+
219
+ def modify(dn, entries, options={})
220
+ dn = ensure_dn_string(dn)
221
+ begin
222
+ operation(options) do
223
+ begin
224
+ yield(dn, entries)
225
+ rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess
226
+ raise OperationNotPermitted, _("%s: %s") % [$!.message, target]
227
+ end
228
+ end
229
+ rescue LdapError::UndefinedType
230
+ raise
231
+ rescue LdapError::ObjectClassViolation
232
+ raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
233
+ end
234
+ end
235
+
236
+ def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
237
+ dn = ensure_dn_string(dn)
238
+ operation(options) do
239
+ yield(dn, new_rdn, delete_old_rdn, new_superior)
240
+ end
241
+ end
242
+
243
+ def log_info(name, runtime_in_seconds, info=nil)
244
+ return unless @logger
245
+ return unless @logger.debug?
246
+ message = "LDAP: #{name} (#{'%.1f' % (runtime_in_seconds * 1000)}ms)"
247
+ @logger.debug(format_log_entry(message, info))
248
+ end
249
+
250
+ private
251
+ def ensure_port(method)
252
+ if method == :ssl
253
+ URI::LDAPS::DEFAULT_PORT
254
+ else
255
+ URI::LDAP::DEFAULT_PORT
256
+ end
257
+ end
258
+
259
+ def prepare_connection(options)
260
+ end
261
+
262
+ def operation(options)
263
+ retried = false
264
+ options = options.dup
265
+ options[:try_reconnect] = true unless options.has_key?(:try_reconnect)
266
+ try_reconnect = false
267
+ begin
268
+ reconnect_if_need(options)
269
+ try_reconnect = options[:try_reconnect]
270
+ with_timeout(try_reconnect, options) do
271
+ yield
272
+ end
273
+ rescue ConnectionError
274
+ if try_reconnect and !retried
275
+ retried = true
276
+ @disconnected = true
277
+ retry
278
+ else
279
+ raise
280
+ end
281
+ end
282
+ end
283
+
284
+ def need_credential_sasl_mechanism?(mechanism)
285
+ not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism)
286
+ end
287
+
288
+ def password(bind_dn, options={})
289
+ passwd = options[:password] || @password
290
+ return passwd if passwd
291
+
292
+ password_block = options[:password_block] || @password_block
293
+ # TODO: Give a warning to reconnect users with password clearing
294
+ # Get the passphrase for the first time, or anew if we aren't storing
295
+ if password_block.respond_to?(:call)
296
+ passwd = password_block.call(bind_dn)
297
+ else
298
+ @logger.error {_('password_block not nil or Proc object. Ignoring.')}
299
+ return nil
300
+ end
301
+
302
+ # Store the password for quick reference later
303
+ if options.has_key?(:store_password)
304
+ store_password = options[:store_password]
305
+ else
306
+ store_password = @store_password
307
+ end
308
+ @password = store_password ? passwd : nil
309
+
310
+ passwd
311
+ end
312
+
313
+ def with_timeout(try_reconnect=true, options={}, &block)
314
+ n_retries = 0
315
+ retry_limit = options[:retry_limit] || @retry_limit
316
+ begin
317
+ do_in_timeout(@timeout, &block)
318
+ rescue Timeout::Error => e
319
+ @logger.error {_('Requested action timed out.')}
320
+ if @retry_on_timeout and retry_limit < 0 and n_retries <= retry_limit
321
+ if connecting?
322
+ retry
323
+ elsif try_reconnect
324
+ retry if with_timeout(false, options) {reconnect(options)}
325
+ end
326
+ end
327
+ @logger.error {e.message}
328
+ raise TimeoutError, e.message
329
+ end
330
+ end
331
+
332
+ def do_in_timeout(timeout, &block)
333
+ Timeout.alarm(timeout, &block)
334
+ end
335
+
336
+ def sasl_bind(bind_dn, options={})
337
+ # Get all SASL mechanisms
338
+ mechanisms = operation(options) do
339
+ root_dse_values("supportedSASLMechanisms")
340
+ end
341
+
342
+ if options.has_key?(:sasl_quiet)
343
+ sasl_quiet = options[:sasl_quiet]
344
+ else
345
+ sasl_quiet = @sasl_quiet
346
+ end
347
+
348
+ sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
349
+ sasl_mechanisms.each do |mechanism|
350
+ next unless mechanisms.include?(mechanism)
351
+ return true if yield(bind_dn, mechanism, sasl_quiet)
352
+ end
353
+ false
354
+ end
355
+
356
+ def simple_bind(bind_dn, options={})
357
+ return false unless bind_dn
358
+
359
+ passwd = password(bind_dn, options)
360
+ return false unless passwd
361
+
362
+ if passwd.empty?
363
+ if options[:allow_anonymous]
364
+ @logger.info {_("Skip simple bind with empty password.")}
365
+ return false
366
+ else
367
+ raise AuthenticationError,
368
+ _("Can't use empty password for simple bind.")
369
+ end
370
+ end
371
+
372
+ begin
373
+ yield(bind_dn, passwd)
374
+ rescue LdapError::InvalidDnSyntax
375
+ raise DistinguishedNameInvalid.new(bind_dn)
376
+ rescue LdapError::InvalidCredentials
377
+ false
378
+ end
379
+ end
380
+
381
+ def parse_filter(filter, operator=nil)
382
+ return nil if filter.nil?
383
+ if !filter.is_a?(String) and !filter.respond_to?(:collect)
384
+ filter = filter.to_s
385
+ end
386
+
387
+ case filter
388
+ when String
389
+ parse_filter_string(filter)
390
+ when Hash
391
+ components = filter.sort_by {|k, v| k.to_s}.collect do |key, value|
392
+ construct_component(key, value, operator)
393
+ end
394
+ construct_filter(components, operator)
395
+ else
396
+ operator, components = normalize_array_filter(filter, operator)
397
+ components = construct_components(components, operator)
398
+ construct_filter(components, operator)
399
+ end
400
+ end
401
+
402
+ def parse_filter_string(filter)
403
+ if /\A\s*\z/.match(filter)
404
+ nil
405
+ else
406
+ if filter[0, 1] == "("
407
+ filter
408
+ else
409
+ "(#{filter})"
410
+ end
411
+ end
412
+ end
413
+
414
+ def normalize_array_filter(filter, operator=nil)
415
+ filter_operator, *components = filter
416
+ if filter_logical_operator?(filter_operator)
417
+ operator = filter_operator
418
+ else
419
+ components.unshift(filter_operator)
420
+ components = [components] unless filter_operator.is_a?(Array)
421
+ end
422
+ [operator, components]
423
+ end
424
+
425
+ def extract_filter_value_options(value)
426
+ options = {}
427
+ if value.is_a?(Array)
428
+ case value[0]
429
+ when Hash
430
+ options = value[0]
431
+ value = value[1]
432
+ when "=", "~=", "<=", ">="
433
+ options[:operator] = value[0]
434
+ if value.size > 2
435
+ value = value[1..-1]
436
+ else
437
+ value = value[1]
438
+ end
439
+ end
440
+ end
441
+ [value, options]
442
+ end
443
+
444
+ def construct_components(components, operator)
445
+ components.collect do |component|
446
+ if component.is_a?(Array)
447
+ if filter_logical_operator?(component[0])
448
+ parse_filter(component)
449
+ elsif component.size == 2
450
+ key, value = component
451
+ if value.is_a?(Hash)
452
+ parse_filter(value, key)
453
+ else
454
+ construct_component(key, value, operator)
455
+ end
456
+ else
457
+ construct_component(component[0], component[1..-1], operator)
458
+ end
459
+ elsif component.is_a?(Symbol)
460
+ assert_filter_logical_operator(component)
461
+ nil
462
+ else
463
+ parse_filter(component, operator)
464
+ end
465
+ end
466
+ end
467
+
468
+ def construct_component(key, value, operator=nil)
469
+ value, options = extract_filter_value_options(value)
470
+ comparison_operator = options[:operator] || "="
471
+ if collection?(value)
472
+ return nil if value.empty?
473
+ operator, value = normalize_array_filter(value, operator)
474
+ values = []
475
+ value.each do |val|
476
+ if collection?(val)
477
+ values.concat(val.collect {|v| [key, comparison_operator, v]})
478
+ else
479
+ values << [key, comparison_operator, val]
480
+ end
481
+ end
482
+ values[0] = values[0][1] if filter_logical_operator?(values[0][1])
483
+ parse_filter(values, operator)
484
+ else
485
+ [
486
+ "(",
487
+ escape_filter_key(key),
488
+ comparison_operator,
489
+ escape_filter_value(value, options),
490
+ ")"
491
+ ].join
492
+ end
493
+ end
494
+
495
+ def escape_filter_key(key)
496
+ escape_filter_value(key.to_s)
497
+ end
498
+
499
+ def escape_filter_value(value, options={})
500
+ case value
501
+ when Numeric, DN
502
+ value = value.to_s
503
+ when Time
504
+ value = Schema::GeneralizedTime.new.normalize_value(value)
505
+ end
506
+ value.gsub(/(?:[:()\\\0]|\*\*?)/) do |s|
507
+ if s == "*"
508
+ s
509
+ else
510
+ s = "*" if s == "**"
511
+ if s.respond_to?(:getbyte)
512
+ "\\%02X" % s.getbyte(0)
513
+ else
514
+ "\\%02X" % s[0]
515
+ end
516
+ end
517
+ end
518
+ end
519
+
520
+ def construct_filter(components, operator=nil)
521
+ operator = normalize_filter_logical_operator(operator)
522
+ components = components.compact
523
+ case components.size
524
+ when 0
525
+ nil
526
+ when 1
527
+ filter = components[0]
528
+ filter = "(!#{filter})" if operator == :not
529
+ filter
530
+ else
531
+ "(#{operator == :and ? '&' : '|'}#{components.join})"
532
+ end
533
+ end
534
+
535
+ def collection?(object)
536
+ !object.is_a?(String) and object.respond_to?(:each)
537
+ end
538
+
539
+ LOGICAL_OPERATORS = [:and, :or, :not, :&, :|]
540
+ def filter_logical_operator?(operator)
541
+ LOGICAL_OPERATORS.include?(operator)
542
+ end
543
+
544
+ def normalize_filter_logical_operator(operator)
545
+ assert_filter_logical_operator(operator)
546
+ case (operator || :and)
547
+ when :and, :&
548
+ :and
549
+ when :or, :|
550
+ :or
551
+ else
552
+ :not
553
+ end
554
+ end
555
+
556
+ def assert_filter_logical_operator(operator)
557
+ return if operator.nil?
558
+ unless filter_logical_operator?(operator)
559
+ raise ArgumentError,
560
+ _("invalid logical operator: %s: available operators: %s") %
561
+ [operator.inspect, LOGICAL_OPERATORS.inspect]
562
+ end
563
+ end
564
+
565
+ # Attempts to reconnect up to the number of times allowed
566
+ # If forced, try once then fail with ConnectionError if not connected.
567
+ def reconnect(options={})
568
+ options = options.dup
569
+ force = options[:force]
570
+ retry_limit = options[:retry_limit] || @retry_limit
571
+ retry_wait = options[:retry_wait] || @retry_wait
572
+ options[:reconnect_attempts] ||= 0
573
+
574
+ loop do
575
+ @logger.debug {_('Attempting to reconnect')}
576
+ disconnect!
577
+
578
+ # Reset the attempts if this was forced.
579
+ options[:reconnect_attempts] = 0 if force
580
+ options[:reconnect_attempts] += 1 if retry_limit >= 0
581
+ begin
582
+ connect(options)
583
+ break
584
+ rescue AuthenticationError
585
+ raise
586
+ rescue => detail
587
+ @logger.error do
588
+ _("Reconnect to server failed: %s\n" \
589
+ "Reconnect to server failed backtrace:\n" \
590
+ "%s") % [detail.exception, detail.backtrace.join("\n")]
591
+ end
592
+ # Do not loop if forced
593
+ raise ConnectionError, detail.message if force
594
+ end
595
+
596
+ unless can_reconnect?(options)
597
+ raise ConnectionError,
598
+ _('Giving up trying to reconnect to LDAP server.')
599
+ end
600
+
601
+ # Sleep before looping
602
+ sleep retry_wait
603
+ end
604
+
605
+ true
606
+ end
607
+
608
+ def reconnect_if_need(options={})
609
+ return if connecting?
610
+ with_timeout(false, options) do
611
+ reconnect(options)
612
+ end
613
+ end
614
+
615
+ # Determine if we have exceed the retry limit or not.
616
+ # True is reconnecting is allowed - False if not.
617
+ def can_reconnect?(options={})
618
+ retry_limit = options[:retry_limit] || @retry_limit
619
+ reconnect_attempts = options[:reconnect_attempts] || 0
620
+
621
+ retry_limit < 0 or reconnect_attempts <= retry_limit
622
+ end
623
+
624
+ def root_dse_values(key, options={})
625
+ dse = root_dse([key], options)
626
+ return [] if dse.nil?
627
+ normalized_key = key.downcase
628
+ dse.each do |_key, _value|
629
+ return _value if _key.downcase == normalized_key
630
+ end
631
+ []
632
+ end
633
+
634
+ def root_dse(attrs, options={})
635
+ found_attributes = nil
636
+ search(:base => "",
637
+ :scope => :base,
638
+ :attributes => attrs,
639
+ :limit => 1) do |dn, attributes|
640
+ found_attributes = attributes
641
+ end
642
+ found_attributes
643
+ end
644
+
645
+ def construct_uri(host, port, ssl)
646
+ protocol = ssl ? "ldaps" : "ldap"
647
+ URI.parse("#{protocol}://#{host}:#{port}").to_s
648
+ end
649
+
650
+ def target
651
+ return nil if @uri.nil?
652
+ if @with_start_tls
653
+ "#{@uri}(StartTLS)"
654
+ else
655
+ @uri
656
+ end
657
+ end
658
+
659
+ def log(name, info=nil)
660
+ if block_given?
661
+ result = nil
662
+ @instrumenter.instrument(
663
+ "log_info.active_ldap",
664
+ :info => info,
665
+ :name => name) { result = yield }
666
+ result
667
+ else
668
+ log_info(name, 0, info)
669
+ nil
670
+ end
671
+ rescue Exception
672
+ log_info("#{name}: FAILED", 0,
673
+ (info || {}).merge(:error => $!.class.name,
674
+ :error_message => $!.message))
675
+ raise
676
+ end
677
+
678
+ def format_log_entry(message, info=nil)
679
+ if ActiveLdap::Base.colorize_logging
680
+ if @@row_even
681
+ message_color, dump_color = "4;36;1", "0;1"
682
+ else
683
+ @@row_even = true
684
+ message_color, dump_color = "4;35;1", "0"
685
+ end
686
+ @@row_even = !@@row_even
687
+
688
+ log_entry = " \e[#{message_color}m#{message}\e[0m"
689
+ log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info
690
+ log_entry
691
+ else
692
+ log_entry = message
693
+ log_entry += ": #{info.inspect}" if info
694
+ log_entry
695
+ end
696
+ end
697
+
698
+ def ensure_dn_string(dn)
699
+ if dn.is_a?(DN)
700
+ dn.to_s
701
+ else
702
+ dn
703
+ end
704
+ end
705
+ end
706
+ end
707
+ end