ruby-activeldap 0.8.3 → 0.8.3.1

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 (177) hide show
  1. data/CHANGES +431 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +58 -0
  4. data/README +104 -0
  5. data/Rakefile +165 -0
  6. data/TODO +22 -0
  7. data/benchmark/bench-al.rb +202 -0
  8. data/benchmark/config.yaml.sample +5 -0
  9. data/data/locale/en/LC_MESSAGES/active-ldap.mo +0 -0
  10. data/data/locale/ja/LC_MESSAGES/active-ldap.mo +0 -0
  11. data/examples/al-admin/README +182 -0
  12. data/examples/al-admin/Rakefile +10 -0
  13. data/examples/al-admin/app/controllers/account_controller.rb +50 -0
  14. data/examples/al-admin/app/controllers/application.rb +15 -0
  15. data/examples/al-admin/app/controllers/directory_controller.rb +22 -0
  16. data/examples/al-admin/app/controllers/users_controller.rb +38 -0
  17. data/examples/al-admin/app/controllers/welcome_controller.rb +4 -0
  18. data/examples/al-admin/app/helpers/account_helper.rb +2 -0
  19. data/examples/al-admin/app/helpers/application_helper.rb +6 -0
  20. data/examples/al-admin/app/helpers/directory_helper.rb +2 -0
  21. data/examples/al-admin/app/helpers/users_helper.rb +13 -0
  22. data/examples/al-admin/app/helpers/welcome_helper.rb +2 -0
  23. data/examples/al-admin/app/models/entry.rb +19 -0
  24. data/examples/al-admin/app/models/ldap_user.rb +49 -0
  25. data/examples/al-admin/app/models/user.rb +91 -0
  26. data/examples/al-admin/app/views/account/login.rhtml +12 -0
  27. data/examples/al-admin/app/views/account/sign_up.rhtml +22 -0
  28. data/examples/al-admin/app/views/directory/index.rhtml +5 -0
  29. data/examples/al-admin/app/views/directory/populate.rhtml +2 -0
  30. data/examples/al-admin/app/views/layouts/application.rhtml +41 -0
  31. data/examples/al-admin/app/views/users/_attribute_information.rhtml +22 -0
  32. data/examples/al-admin/app/views/users/_entry.rhtml +12 -0
  33. data/examples/al-admin/app/views/users/_form.rhtml +29 -0
  34. data/examples/al-admin/app/views/users/_object_class_information.rhtml +23 -0
  35. data/examples/al-admin/app/views/users/edit.rhtml +10 -0
  36. data/examples/al-admin/app/views/users/index.rhtml +9 -0
  37. data/examples/al-admin/app/views/users/show.rhtml +3 -0
  38. data/examples/al-admin/app/views/welcome/index.rhtml +16 -0
  39. data/examples/al-admin/config/boot.rb +45 -0
  40. data/examples/al-admin/config/database.yml.example +19 -0
  41. data/examples/al-admin/config/environment.rb +68 -0
  42. data/examples/al-admin/config/environments/development.rb +21 -0
  43. data/examples/al-admin/config/environments/production.rb +18 -0
  44. data/examples/al-admin/config/environments/test.rb +19 -0
  45. data/examples/al-admin/config/ldap.yml.example +21 -0
  46. data/examples/al-admin/config/routes.rb +26 -0
  47. data/examples/al-admin/db/migrate/001_create_users.rb +16 -0
  48. data/examples/al-admin/lib/accept_http_rails_relative_url_root.rb +9 -0
  49. data/examples/al-admin/lib/authenticated_system.rb +131 -0
  50. data/examples/al-admin/lib/authenticated_test_helper.rb +113 -0
  51. data/examples/al-admin/lib/tasks/gettext.rake +35 -0
  52. data/examples/al-admin/po/en/al-admin.po +190 -0
  53. data/examples/al-admin/po/ja/al-admin.po +190 -0
  54. data/examples/al-admin/po/nl/al-admin.po +202 -0
  55. data/examples/al-admin/public/.htaccess +40 -0
  56. data/examples/al-admin/public/404.html +30 -0
  57. data/examples/al-admin/public/500.html +30 -0
  58. data/examples/al-admin/public/dispatch.cgi +10 -0
  59. data/examples/al-admin/public/dispatch.fcgi +24 -0
  60. data/examples/al-admin/public/dispatch.rb +10 -0
  61. data/examples/al-admin/public/favicon.ico +0 -0
  62. data/examples/al-admin/public/images/rails.png +0 -0
  63. data/examples/al-admin/public/javascripts/application.js +2 -0
  64. data/examples/al-admin/public/javascripts/controls.js +833 -0
  65. data/examples/al-admin/public/javascripts/dragdrop.js +942 -0
  66. data/examples/al-admin/public/javascripts/effects.js +1088 -0
  67. data/examples/al-admin/public/javascripts/prototype.js +2515 -0
  68. data/examples/al-admin/public/robots.txt +1 -0
  69. data/examples/al-admin/public/stylesheets/rails.css +35 -0
  70. data/examples/al-admin/public/stylesheets/screen.css +52 -0
  71. data/examples/al-admin/script/about +3 -0
  72. data/examples/al-admin/script/breakpointer +3 -0
  73. data/examples/al-admin/script/console +3 -0
  74. data/examples/al-admin/script/destroy +3 -0
  75. data/examples/al-admin/script/generate +3 -0
  76. data/examples/al-admin/script/performance/benchmarker +3 -0
  77. data/examples/al-admin/script/performance/profiler +3 -0
  78. data/examples/al-admin/script/plugin +3 -0
  79. data/examples/al-admin/script/process/inspector +3 -0
  80. data/examples/al-admin/script/process/reaper +3 -0
  81. data/examples/al-admin/script/process/spawner +3 -0
  82. data/examples/al-admin/script/runner +3 -0
  83. data/examples/al-admin/script/server +3 -0
  84. data/examples/al-admin/test/fixtures/users.yml +9 -0
  85. data/examples/al-admin/test/functional/account_controller_test.rb +24 -0
  86. data/examples/al-admin/test/functional/directory_controller_test.rb +18 -0
  87. data/examples/al-admin/test/functional/users_controller_test.rb +18 -0
  88. data/examples/al-admin/test/functional/welcome_controller_test.rb +18 -0
  89. data/examples/al-admin/test/run-test.sh +3 -0
  90. data/examples/al-admin/test/test_helper.rb +28 -0
  91. data/examples/al-admin/test/unit/user_test.rb +13 -0
  92. data/examples/al-admin/vendor/plugins/exception_notification/README +111 -0
  93. data/examples/al-admin/vendor/plugins/exception_notification/init.rb +1 -0
  94. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifiable.rb +99 -0
  95. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier.rb +67 -0
  96. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb +77 -0
  97. data/examples/al-admin/vendor/plugins/exception_notification/test/exception_notifier_helper_test.rb +61 -0
  98. data/examples/al-admin/vendor/plugins/exception_notification/test/test_helper.rb +7 -0
  99. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml +1 -0
  100. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml +7 -0
  101. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml +16 -0
  102. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml +3 -0
  103. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml +2 -0
  104. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml +3 -0
  105. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml +6 -0
  106. data/examples/config.yaml.example +5 -0
  107. data/examples/example.der +0 -0
  108. data/examples/example.jpg +0 -0
  109. data/examples/groupadd +41 -0
  110. data/examples/groupdel +35 -0
  111. data/examples/groupls +49 -0
  112. data/examples/groupmod +42 -0
  113. data/examples/lpasswd +55 -0
  114. data/examples/objects/group.rb +13 -0
  115. data/examples/objects/ou.rb +4 -0
  116. data/examples/objects/user.rb +20 -0
  117. data/examples/ouadd +38 -0
  118. data/examples/useradd +45 -0
  119. data/examples/useradd-binary +50 -0
  120. data/examples/userdel +34 -0
  121. data/examples/userls +50 -0
  122. data/examples/usermod +42 -0
  123. data/examples/usermod-binary-add +47 -0
  124. data/examples/usermod-binary-add-time +51 -0
  125. data/examples/usermod-binary-del +48 -0
  126. data/examples/usermod-lang-add +43 -0
  127. data/lib/active_ldap.rb +978 -0
  128. data/lib/active_ldap/adapter/base.rb +512 -0
  129. data/lib/active_ldap/adapter/ldap.rb +233 -0
  130. data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
  131. data/lib/active_ldap/adapter/net_ldap.rb +290 -0
  132. data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
  133. data/lib/active_ldap/association/belongs_to.rb +47 -0
  134. data/lib/active_ldap/association/belongs_to_many.rb +42 -0
  135. data/lib/active_ldap/association/collection.rb +83 -0
  136. data/lib/active_ldap/association/has_many.rb +31 -0
  137. data/lib/active_ldap/association/has_many_utils.rb +35 -0
  138. data/lib/active_ldap/association/has_many_wrap.rb +46 -0
  139. data/lib/active_ldap/association/proxy.rb +102 -0
  140. data/lib/active_ldap/associations.rb +172 -0
  141. data/lib/active_ldap/attributes.rb +211 -0
  142. data/lib/active_ldap/base.rb +1256 -0
  143. data/lib/active_ldap/callbacks.rb +19 -0
  144. data/lib/active_ldap/command.rb +48 -0
  145. data/lib/active_ldap/configuration.rb +114 -0
  146. data/lib/active_ldap/connection.rb +234 -0
  147. data/lib/active_ldap/distinguished_name.rb +250 -0
  148. data/lib/active_ldap/escape.rb +12 -0
  149. data/lib/active_ldap/get_text/parser.rb +142 -0
  150. data/lib/active_ldap/get_text_fallback.rb +53 -0
  151. data/lib/active_ldap/get_text_support.rb +12 -0
  152. data/lib/active_ldap/helper.rb +23 -0
  153. data/lib/active_ldap/ldap_error.rb +74 -0
  154. data/lib/active_ldap/object_class.rb +93 -0
  155. data/lib/active_ldap/operations.rb +419 -0
  156. data/lib/active_ldap/populate.rb +44 -0
  157. data/lib/active_ldap/schema.rb +427 -0
  158. data/lib/active_ldap/timeout.rb +75 -0
  159. data/lib/active_ldap/timeout_stub.rb +17 -0
  160. data/lib/active_ldap/user_password.rb +93 -0
  161. data/lib/active_ldap/validations.rb +112 -0
  162. data/po/en/active-ldap.po +3011 -0
  163. data/po/ja/active-ldap.po +3044 -0
  164. data/rails/plugin/active_ldap/README +54 -0
  165. data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
  166. data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
  167. data/rails/plugin/active_ldap/init.rb +19 -0
  168. data/test/al-test-utils.rb +362 -0
  169. data/test/command.rb +62 -0
  170. data/test/config.yaml.sample +6 -0
  171. data/test/run-test.rb +31 -0
  172. data/test/test-unit-ext.rb +4 -0
  173. data/test/test-unit-ext/always-show-result.rb +28 -0
  174. data/test/test-unit-ext/backtrace-filter.rb +17 -0
  175. data/test/test-unit-ext/long-display-for-emacs.rb +25 -0
  176. data/test/test-unit-ext/priority.rb +163 -0
  177. metadata +211 -4
@@ -0,0 +1,512 @@
1
+ require 'active_ldap/schema'
2
+ require 'active_ldap/ldap_error'
3
+
4
+ module ActiveLdap
5
+ module Adapter
6
+ class Base
7
+ include GetTextSupport
8
+
9
+ VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :timeout,
10
+ :retry_on_timeout, :retry_limit,
11
+ :retry_wait, :bind_dn, :password,
12
+ :password_block, :try_sasl,
13
+ :sasl_mechanisms, :sasl_quiet,
14
+ :allow_anonymous, :store_password,
15
+ :scope]
16
+ def initialize(configuration={})
17
+ @connection = nil
18
+ @configuration = configuration.dup
19
+ @logger = @configuration.delete(:logger)
20
+ @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
21
+ VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
22
+ instance_variable_set("@#{name}", configuration[name])
23
+ end
24
+ end
25
+
26
+ def connect(options={})
27
+ host = options[:host] || @host
28
+ port = options[:port] || @port
29
+ method = ensure_method(options[:method] || @method)
30
+ @connection = yield(host, port, method)
31
+ prepare_connection(options)
32
+ bind(options)
33
+ end
34
+
35
+ def disconnect!(options={})
36
+ return if @connection.nil?
37
+ unbind(options)
38
+ @connection = nil
39
+ end
40
+
41
+ def rebind(options={})
42
+ unbind(options) if bound?
43
+ connect(options)
44
+ end
45
+
46
+ def bind(options={})
47
+ bind_dn = options[:bind_dn] || @bind_dn
48
+ try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
49
+ if options.has_key?(:allow_anonymous)
50
+ allow_anonymous = options[:allow_anonymous]
51
+ else
52
+ allow_anonymous = @allow_anonymous
53
+ end
54
+
55
+ # Rough bind loop:
56
+ # Attempt 1: SASL if available
57
+ # Attempt 2: SIMPLE with credentials if password block
58
+ # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
59
+ if try_sasl and sasl_bind(bind_dn, options)
60
+ @logger.info {_('Bound by SASL as %s') % bind_dn}
61
+ elsif simple_bind(bind_dn, options)
62
+ @logger.info {_('Bound by simple as %s') % bind_dn}
63
+ elsif allow_anonymous and bind_as_anonymous(options)
64
+ @logger.info {_('Bound as anonymous')}
65
+ else
66
+ message = yield if block_given?
67
+ message ||= _('All authentication methods exhausted.')
68
+ raise AuthenticationError, message
69
+ end
70
+
71
+ bound?
72
+ end
73
+
74
+ def bind_as_anonymous(options={})
75
+ operation(options) do
76
+ yield
77
+ end
78
+ end
79
+
80
+ def connecting?
81
+ not @connection.nil?
82
+ end
83
+
84
+ def schema(options={})
85
+ @schema ||= operation(options) do
86
+ base = options[:base]
87
+ attrs = options[:attributes]
88
+
89
+ attrs ||= [
90
+ 'objectClasses',
91
+ 'attributeTypes',
92
+ 'matchingRules',
93
+ 'matchingRuleUse',
94
+ 'dITStructureRules',
95
+ 'dITContentRules',
96
+ 'nameForms',
97
+ 'ldapSyntaxes',
98
+ #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER.
99
+ ]
100
+ base ||= root_dse_values('subschemaSubentry', options)[0]
101
+ base ||= 'cn=schema'
102
+ dn, attributes = search(:base => base,
103
+ :scope => :base,
104
+ :filter => '(objectClass=subschema)',
105
+ :attributes => attrs).first
106
+ Schema.new(attributes)
107
+ end
108
+ end
109
+
110
+ def load(ldifs, options={})
111
+ operation(options) do
112
+ ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
113
+ yield(ldif)
114
+ end
115
+ end
116
+ end
117
+
118
+ def search(options={})
119
+ filter = parse_filter(options[:filter]) || 'objectClass=*'
120
+ attrs = options[:attributes] || []
121
+ scope = ensure_scope(options[:scope] || @scope)
122
+ base = options[:base]
123
+ limit = options[:limit] || 0
124
+ limit = nil if limit <= 0
125
+
126
+ attrs = attrs.to_a # just in case
127
+
128
+ values = []
129
+ callback = Proc.new do |value, block|
130
+ value = block.call(value) if block
131
+ values << value
132
+ end
133
+
134
+ begin
135
+ operation(options) do
136
+ yield(base, scope, filter, attrs, limit, callback)
137
+ end
138
+ rescue LdapError
139
+ # Do nothing on failure
140
+ @logger.info do
141
+ args = [$!.class, $!.message, filter, attrs.inspect]
142
+ _("Ignore error %s(%s): filter %s: attributes: %s") % args
143
+ end
144
+ end
145
+
146
+ values
147
+ end
148
+
149
+ def delete(targets, options={})
150
+ targets = [targets] unless targets.is_a?(Array)
151
+ return if targets.empty?
152
+ target = nil
153
+ begin
154
+ operation(options) do
155
+ targets.each do |target|
156
+ yield(target)
157
+ end
158
+ end
159
+ rescue LdapError::NoSuchObject
160
+ raise EntryNotFound, _("No such entry: %s") % target
161
+ end
162
+ end
163
+
164
+ def add(dn, entries, options={})
165
+ begin
166
+ operation(options) do
167
+ yield(dn, entries)
168
+ end
169
+ rescue LdapError::NoSuchObject
170
+ raise EntryNotFound, _("No such entry: %s") % dn
171
+ rescue LdapError::InvalidDnSyntax
172
+ raise DistinguishedNameInvalid.new(dn)
173
+ rescue LdapError::AlreadyExists
174
+ raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn]
175
+ rescue LdapError::StrongAuthRequired
176
+ raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn]
177
+ rescue LdapError::ObjectClassViolation
178
+ raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
179
+ rescue LdapError::UnwillingToPerform
180
+ raise OperationNotPermitted, _("%s: %s") % [$!.message, dn]
181
+ end
182
+ end
183
+
184
+ def modify(dn, entries, options={})
185
+ begin
186
+ operation(options) do
187
+ yield(dn, entries)
188
+ end
189
+ rescue LdapError::UndefinedType
190
+ raise
191
+ rescue LdapError::ObjectClassViolation
192
+ raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
193
+ end
194
+ end
195
+
196
+ private
197
+ def prepare_connection(options)
198
+ end
199
+
200
+ def operation(options)
201
+ reconnect_if_need
202
+ try_reconnect = !options.has_key?(:try_reconnect) ||
203
+ options[:try_reconnect]
204
+ with_timeout(try_reconnect, options) do
205
+ yield
206
+ end
207
+ end
208
+
209
+ def need_credential_sasl_mechanism?(mechanism)
210
+ not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism)
211
+ end
212
+
213
+ def password(bind_dn, options={})
214
+ passwd = options[:password] || @password
215
+ return passwd if passwd
216
+
217
+ password_block = options[:password_block] || @password_block
218
+ # TODO: Give a warning to reconnect users with password clearing
219
+ # Get the passphrase for the first time, or anew if we aren't storing
220
+ if password_block.respond_to?(:call)
221
+ passwd = password_block.call(bind_dn)
222
+ else
223
+ @logger.error {_('password_block not nil or Proc object. Ignoring.')}
224
+ return nil
225
+ end
226
+
227
+ # Store the password for quick reference later
228
+ if options.has_key?(:store_password)
229
+ store_password = options[:store_password]
230
+ else
231
+ store_password = @store_password
232
+ end
233
+ @password = store_password ? passwd : nil
234
+
235
+ passwd
236
+ end
237
+
238
+ def with_timeout(try_reconnect=true, options={}, &block)
239
+ begin
240
+ Timeout.alarm(@timeout, &block)
241
+ rescue Timeout::Error => e
242
+ @logger.error {_('Requested action timed out.')}
243
+ retry if try_reconnect and @retry_on_timeout and reconnect(options)
244
+ @logger.error {e.message}
245
+ raise TimeoutError, e.message
246
+ end
247
+ end
248
+
249
+ def sasl_bind(bind_dn, options={})
250
+ return false unless bind_dn
251
+
252
+ # Get all SASL mechanisms
253
+ mechanisms = operation(options) do
254
+ root_dse_values("supportedSASLMechanisms")
255
+ end
256
+
257
+ if options.has_key?(:sasl_quiet)
258
+ sasl_quiet = options[:sasl_quiet]
259
+ else
260
+ sasl_quiet = @sasl_quiet
261
+ end
262
+
263
+ sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
264
+ sasl_mechanisms.each do |mechanism|
265
+ next unless mechanisms.include?(mechanism)
266
+ operation(options) do
267
+ yield(bind_dn, mechanism, sasl_quiet)
268
+ return true if bound?
269
+ end
270
+ end
271
+ false
272
+ end
273
+
274
+ def simple_bind(bind_dn, options={})
275
+ return false unless bind_dn
276
+
277
+ passwd = password(bind_dn, options)
278
+ return false unless passwd
279
+
280
+ begin
281
+ operation(options) do
282
+ yield(bind_dn, passwd)
283
+ bound?
284
+ end
285
+ rescue LdapError::InvalidDnSyntax
286
+ raise DistinguishedNameInvalid.new(bind_dn)
287
+ rescue LdapError::InvalidCredentials
288
+ false
289
+ end
290
+ end
291
+
292
+ def parse_filter(filter, operator=nil)
293
+ return nil if filter.nil?
294
+ if !filter.is_a?(String) and !filter.respond_to?(:collect)
295
+ filter = filter.to_s
296
+ end
297
+
298
+ case filter
299
+ when String
300
+ parse_filter_string(filter)
301
+ when Hash
302
+ components = filter.sort_by {|k, v| k.to_s}.collect do |key, value|
303
+ construct_component(key, value, operator)
304
+ end
305
+ construct_filter(components, operator)
306
+ else
307
+ operator, components = normalize_array_filter(filter, operator)
308
+
309
+ components = components.collect do |component|
310
+ if component.is_a?(Array) and component.size == 2
311
+ key, value = component
312
+ if filter_logical_operator?(key)
313
+ parse_filter(component)
314
+ elsif value.is_a?(Hash)
315
+ parse_filter(value, key)
316
+ else
317
+ construct_component(key, value, operator)
318
+ end
319
+ elsif component.is_a?(Symbol)
320
+ assert_filter_logical_operator(component)
321
+ nil
322
+ else
323
+ parse_filter(component, operator)
324
+ end
325
+ end
326
+ construct_filter(components, operator)
327
+ end
328
+ end
329
+
330
+ def parse_filter_string(filter)
331
+ if /\A\s*\z/.match(filter)
332
+ nil
333
+ else
334
+ if filter[0, 1] == "("
335
+ filter
336
+ else
337
+ "(#{filter})"
338
+ end
339
+ end
340
+ end
341
+
342
+ def normalize_array_filter(filter, operator=nil)
343
+ filter_operator, *components = filter
344
+ if filter_logical_operator?(filter_operator)
345
+ operator = filter_operator
346
+ else
347
+ components.unshift(filter_operator)
348
+ end
349
+ [operator, components]
350
+ end
351
+
352
+ def extract_filter_value_options(value)
353
+ options = {}
354
+ if value.is_a?(Array)
355
+ case value[0]
356
+ when Hash
357
+ options = value[0]
358
+ value = value[1]
359
+ when "=", "~=", "<=", "=>"
360
+ options[:operator] = value[1]
361
+ value = value[1]
362
+ end
363
+ end
364
+ [value, options]
365
+ end
366
+
367
+ def construct_component(key, value, operator=nil)
368
+ value, options = extract_filter_value_options(value)
369
+ if collection?(value)
370
+ values = []
371
+ value.each do |val|
372
+ if collection?(val)
373
+ values.concat(val.collect {|v| [key, v]})
374
+ else
375
+ values << [key, val]
376
+ end
377
+ end
378
+ values[0] = values[0][1] if filter_logical_operator?(values[0][1])
379
+ parse_filter(values, operator)
380
+ else
381
+ [
382
+ "(",
383
+ escape_filter_key(key),
384
+ options[:operator] || "=",
385
+ escape_filter_value(value, options),
386
+ ")"
387
+ ].join
388
+ end
389
+ end
390
+
391
+ def escape_filter_key(key)
392
+ escape_filter_value(key.to_s)
393
+ end
394
+
395
+ def escape_filter_value(value, options={})
396
+ value.gsub(/(?:[()\\\0]|\*\*?)/) do |s|
397
+ if s == "*"
398
+ s
399
+ else
400
+ s = "*" if s == "**"
401
+ "\\%02X" % s[0]
402
+ end
403
+ end
404
+ end
405
+
406
+ def construct_filter(components, operator=nil)
407
+ operator = normalize_filter_logical_operator(operator)
408
+ components = components.compact
409
+ case components.size
410
+ when 0
411
+ nil
412
+ when 1
413
+ filter = components[0]
414
+ filter = "(!#{filter})" if operator == :not
415
+ filter
416
+ else
417
+ "(#{operator == :and ? '&' : '|'}#{components.join})"
418
+ end
419
+ end
420
+
421
+ def collection?(object)
422
+ !object.is_a?(String) and object.respond_to?(:each)
423
+ end
424
+
425
+ LOGICAL_OPERATORS = [:and, :or, :not, :&, :|]
426
+ def filter_logical_operator?(operator)
427
+ LOGICAL_OPERATORS.include?(operator)
428
+ end
429
+
430
+ def normalize_filter_logical_operator(operator)
431
+ assert_filter_logical_operator(operator)
432
+ case (operator || :and)
433
+ when :and, :&
434
+ :and
435
+ when :or, :|
436
+ :or
437
+ else
438
+ :not
439
+ end
440
+ end
441
+
442
+ def assert_filter_logical_operator(operator)
443
+ return if operator.nil?
444
+ unless filter_logical_operator?(operator)
445
+ raise ArgumentError,
446
+ _("invalid logical operator: %s: available operators: %s") %
447
+ [operator.inspect, LOGICAL_OPERATORS.inspect]
448
+ end
449
+ end
450
+
451
+ # Attempts to reconnect up to the number of times allowed
452
+ # If forced, try once then fail with ConnectionError if not connected.
453
+ def reconnect(options={})
454
+ options = options.dup
455
+ force = options[:force]
456
+ retry_limit = options[:retry_limit] || @retry_limit
457
+ retry_wait = options[:retry_wait] || @retry_wait
458
+ options[:reconnect_attempts] ||= 0
459
+
460
+ loop do
461
+ unless can_reconnect?(options)
462
+ raise ConnectionError,
463
+ _('Giving up trying to reconnect to LDAP server.')
464
+ end
465
+
466
+ @logger.debug {_('Attempting to reconnect')}
467
+ disconnect!
468
+
469
+ # Reset the attempts if this was forced.
470
+ options[:reconnect_attempts] = 0 if force
471
+ options[:reconnect_attempts] += 1 if retry_limit >= 0
472
+ begin
473
+ connect(options)
474
+ break
475
+ rescue => detail
476
+ @logger.error do
477
+ _("Reconnect to server failed: %s\n" \
478
+ "Reconnect to server failed backtrace:\n" \
479
+ "%s") % [detail.exception, detail.backtrace.join("\n")]
480
+ end
481
+ # Do not loop if forced
482
+ raise ConnectionError, detail.message if force
483
+ end
484
+
485
+ # Sleep before looping
486
+ sleep retry_wait
487
+ end
488
+
489
+ true
490
+ end
491
+
492
+ def reconnect_if_need(options={})
493
+ reconnect(options) if !connecting? and can_reconnect?(options)
494
+ end
495
+
496
+ # Determine if we have exceed the retry limit or not.
497
+ # True is reconnecting is allowed - False if not.
498
+ def can_reconnect?(options={})
499
+ retry_limit = options[:retry_limit] || @retry_limit
500
+ reconnect_attempts = options[:reconnect_attempts] || 0
501
+
502
+ retry_limit < 0 or reconnect_attempts < (retry_limit - 1)
503
+ end
504
+
505
+ def root_dse_values(key, options={})
506
+ dse = root_dse([key], options)[0]
507
+ return [] if dse.nil?
508
+ dse[key] || dse[key.downcase] || []
509
+ end
510
+ end
511
+ end
512
+ end