activeldap 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/CHANGES +61 -0
  2. data/README +8 -1
  3. data/Rakefile +4 -1
  4. data/benchmark/bench-al.rb +12 -2
  5. data/examples/al-admin/app/controllers/account_controller.rb +4 -3
  6. data/examples/al-admin/app/controllers/application.rb +5 -2
  7. data/examples/al-admin/app/controllers/directory_controller.rb +3 -1
  8. data/examples/al-admin/app/controllers/users_controller.rb +19 -4
  9. data/examples/al-admin/app/controllers/welcome_controller.rb +4 -2
  10. data/examples/al-admin/app/helpers/application_helper.rb +7 -1
  11. data/examples/al-admin/app/helpers/url_helper.rb +4 -0
  12. data/examples/al-admin/app/models/ldap_user.rb +4 -0
  13. data/examples/al-admin/app/views/_entry/{_attributes_information.rhtml → _attributes_information.html.erb} +0 -0
  14. data/examples/al-admin/app/views/_entry/{_entry.rhtml → _entry.html.erb} +0 -0
  15. data/examples/al-admin/app/views/_schema/{_aliases.rhtml → _aliases.html.erb} +0 -0
  16. data/examples/al-admin/app/views/_switcher/{_after.rhtml → _after.html.erb} +0 -0
  17. data/examples/al-admin/app/views/_switcher/{_before.rhtml → _before.html.erb} +0 -0
  18. data/examples/al-admin/app/views/account/{login.rhtml → login.html.erb} +0 -0
  19. data/examples/al-admin/app/views/account/{sign_up.rhtml → sign_up.html.erb} +0 -0
  20. data/examples/al-admin/app/views/attributes/{_attributes.rhtml → _attributes.html.erb} +0 -0
  21. data/examples/al-admin/app/views/attributes/{_detail.rhtml → _detail.html.erb} +0 -0
  22. data/examples/al-admin/app/views/attributes/{index.rhtml → index.html.erb} +0 -0
  23. data/examples/al-admin/app/views/attributes/{show.rhtml → show.html.erb} +0 -0
  24. data/examples/al-admin/app/views/directory/{_tree.rhtml → _tree.html.erb} +0 -0
  25. data/examples/al-admin/app/views/directory/{_tree_view_js.rhtml → _tree_view_js.html.erb} +4 -5
  26. data/examples/al-admin/app/views/directory/{index.rhtml → index.html.erb} +0 -0
  27. data/examples/al-admin/app/views/directory/{populate.rhtml → populate.html.erb} +0 -0
  28. data/examples/al-admin/app/views/layouts/{_footer.rhtml → _footer.html.erb} +0 -0
  29. data/examples/al-admin/app/views/layouts/{_header_menu.rhtml → _header_menu.html.erb} +0 -0
  30. data/examples/al-admin/app/views/layouts/{_main_menu.rhtml → _main_menu.html.erb} +0 -0
  31. data/examples/al-admin/app/views/layouts/{application.rhtml → application.html.erb} +3 -2
  32. data/examples/al-admin/app/views/object_classes/{_attributes.rhtml → _attributes.html.erb} +0 -0
  33. data/examples/al-admin/app/views/object_classes/{_object_classes.rhtml → _object_classes.html.erb} +0 -0
  34. data/examples/al-admin/app/views/object_classes/{index.rhtml → index.html.erb} +0 -0
  35. data/examples/al-admin/app/views/object_classes/{show.rhtml → show.html.erb} +0 -0
  36. data/examples/al-admin/app/views/syntaxes/{_detail.rhtml → _detail.html.erb} +0 -0
  37. data/examples/al-admin/app/views/syntaxes/{_syntaxes.rhtml → _syntaxes.html.erb} +0 -0
  38. data/examples/al-admin/app/views/syntaxes/{index.rhtml → index.html.erb} +0 -0
  39. data/examples/al-admin/app/views/syntaxes/{show.rhtml → show.html.erb} +0 -0
  40. data/examples/al-admin/app/views/users/{_attributes_update_form.rhtml → _attributes_update_form.html.erb} +0 -0
  41. data/examples/al-admin/app/views/users/{_form.rhtml → _form.html.erb} +0 -0
  42. data/examples/al-admin/app/views/users/{_object_classes_update_form.rhtml → _object_classes_update_form.html.erb} +7 -1
  43. data/examples/al-admin/app/views/users/{_password_change_form.rhtml → _password_change_form.html.erb} +0 -0
  44. data/examples/al-admin/app/views/users/{edit.rhtml → edit.html.erb} +0 -0
  45. data/examples/al-admin/app/views/users/{index.rhtml → index.html.erb} +0 -0
  46. data/examples/al-admin/app/views/users/{show.rhtml → show.html.erb} +0 -0
  47. data/examples/al-admin/app/views/welcome/{index.rhtml → index.html.erb} +0 -0
  48. data/examples/al-admin/config/boot.rb +96 -32
  49. data/examples/al-admin/config/environment.rb +30 -36
  50. data/examples/al-admin/config/environments/development.rb +2 -5
  51. data/examples/al-admin/config/environments/production.rb +1 -0
  52. data/examples/al-admin/config/environments/test.rb +4 -1
  53. data/examples/al-admin/config/initializers/exception_notifier.rb +2 -0
  54. data/examples/al-admin/config/initializers/gettext.rb +1 -0
  55. data/examples/al-admin/config/initializers/inflections.rb +10 -0
  56. data/examples/al-admin/config/initializers/mime_types.rb +5 -0
  57. data/examples/al-admin/config/initializers/ralative_url_support.rb +1 -0
  58. data/examples/al-admin/config/routes.rb +24 -12
  59. data/examples/al-admin/lib/authenticated_system.rb +1 -1
  60. data/examples/al-admin/lib/tasks/gettext.rake +1 -1
  61. data/examples/al-admin/po/en/al-admin.po +102 -100
  62. data/examples/al-admin/po/ja/al-admin.po +112 -110
  63. data/examples/al-admin/po/nl/al-admin.po +117 -110
  64. data/examples/al-admin/public/javascripts/controls.js +484 -354
  65. data/examples/al-admin/public/javascripts/dragdrop.js +88 -58
  66. data/examples/al-admin/public/javascripts/effects.js +396 -364
  67. data/examples/al-admin/public/javascripts/prototype.js +2817 -1107
  68. data/examples/al-admin/public/stylesheets/base.css +5 -0
  69. data/examples/al-admin/script/performance/request +3 -0
  70. data/lib/active_ldap.rb +13 -10
  71. data/lib/active_ldap/adapter/base.rb +159 -43
  72. data/lib/active_ldap/adapter/jndi.rb +175 -0
  73. data/lib/active_ldap/adapter/jndi_connection.rb +180 -0
  74. data/lib/active_ldap/adapter/ldap.rb +91 -46
  75. data/lib/active_ldap/adapter/ldap_ext.rb +19 -5
  76. data/lib/active_ldap/adapter/net_ldap.rb +52 -44
  77. data/lib/active_ldap/association/has_many_wrap.rb +1 -1
  78. data/lib/active_ldap/attributes.rb +20 -95
  79. data/lib/active_ldap/base.rb +195 -186
  80. data/lib/active_ldap/callbacks.rb +33 -0
  81. data/lib/active_ldap/command.rb +3 -3
  82. data/lib/active_ldap/connection.rb +21 -3
  83. data/lib/active_ldap/distinguished_name.rb +18 -11
  84. data/lib/active_ldap/entry_attribute.rb +78 -0
  85. data/lib/active_ldap/human_readable.rb +20 -0
  86. data/lib/active_ldap/ldif.rb +860 -10
  87. data/lib/active_ldap/object_class.rb +6 -4
  88. data/lib/active_ldap/operations.rb +129 -22
  89. data/lib/active_ldap/schema.rb +118 -9
  90. data/lib/active_ldap/schema/syntaxes.rb +33 -16
  91. data/lib/active_ldap/validations.rb +74 -65
  92. data/po/en/active-ldap.po +378 -768
  93. data/po/ja/active-ldap.po +935 -868
  94. data/rails/plugin/active_ldap/init.rb +40 -2
  95. data/test/al-test-utils.rb +78 -58
  96. data/test/command.rb +51 -1
  97. data/test/test-unit-ext/priority.rb +29 -6
  98. data/test/test_adapter.rb +21 -2
  99. data/test/test_attributes.rb +13 -0
  100. data/test/test_base.rb +51 -1
  101. data/test/test_connection.rb +2 -1
  102. data/test/test_connection_per_class.rb +55 -1
  103. data/test/test_connection_per_dn.rb +29 -1
  104. data/test/test_find.rb +73 -0
  105. data/test/test_ldif.rb +1829 -15
  106. data/test/test_load.rb +126 -0
  107. data/test/test_object_class.rb +23 -5
  108. data/test/test_schema.rb +28 -0
  109. data/test/test_syntax.rb +22 -11
  110. data/test/test_user.rb +16 -25
  111. data/test/test_useradd-binary.rb +1 -1
  112. data/test/test_usermod-binary-add-time.rb +1 -1
  113. data/test/test_usermod-binary-add.rb +1 -1
  114. data/test/test_validation.rb +100 -22
  115. metadata +77 -71
  116. data/data/locale/en/LC_MESSAGES/active-ldap.mo +0 -0
  117. data/data/locale/ja/LC_MESSAGES/active-ldap.mo +0 -0
  118. data/examples/al-admin/app/views/layouts/_flash_box.rhtml +0 -4
  119. data/examples/al-admin/public/stylesheets/common.css +0 -2
  120. data/examples/al-admin/script/breakpointer +0 -3
@@ -34,6 +34,11 @@ h1
34
34
  background-color: #cfc;
35
35
  }
36
36
 
37
+ div.flash-box-container
38
+ {
39
+ position: relative;
40
+ }
41
+
37
42
  div#flash-box
38
43
  {
39
44
  position: absolute;
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/request'
@@ -192,9 +192,9 @@
192
192
  #
193
193
  # Under ou=People I store user objects, and under ou=Groups, I store group
194
194
  # objects. What |ldap_mapping| has done is mapped the class in to the LDAP tree
195
- # abstractly. With the given :dnattr and :prefix, it will only work for entries
196
- # under ou=Groups,dc=dataspill,dc=org using the primary attribute 'cn' as the
197
- # beginning of the distinguished name.
195
+ # abstractly. With the given :dn_attributes and :prefix, it will only work for
196
+ # entries under ou=Groups,dc=dataspill,dc=org using the primary attribute 'cn'
197
+ # as the beginning of the distinguished name.
198
198
  #
199
199
  # Just for clarity, here's how the arguments map out:
200
200
  #
@@ -205,7 +205,8 @@
205
205
  # :base from configuration.rb
206
206
  #
207
207
  # :scope tells ActiveLdap to only search under ou=Groups, and not to look deeper
208
- # for dnattr matches. (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
208
+ # for dn_attribute matches.
209
+ # (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
209
210
  #
210
211
  # Something's missing: :classes. :classes is used to tell Ruby/ActiveLdap what
211
212
  # the minimum requirement is when creating a new object. LDAP uses objectClasses
@@ -293,7 +294,7 @@
293
294
  # mind, the above definition could become:
294
295
  #
295
296
  # irb> class User < ActiveLdap::Base
296
- # irb* ldap_mapping :dnattr => 'uid', :prefix => 'People', :classes => ['top','account']
297
+ # irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'People', :classes => ['top','account']
297
298
  # irb* belongs_to :groups, :class => 'Group', :many => 'memberUid'
298
299
  # irb* end
299
300
  #
@@ -348,8 +349,8 @@
348
349
  # => "root"
349
350
  #
350
351
  # In this simple example, Group.find took the search string of 'deve*' and
351
- # searched for the first match in Group where the dnattr matched the query. This
352
- # is the simplest example of .find.
352
+ # searched for the first match in Group where the dn_attribute matched the
353
+ # query. This is the simplest example of .find.
353
354
  #
354
355
  # irb> Group.find(:all, '*').collect {|group| group.cn}
355
356
  # => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
@@ -921,7 +922,7 @@ if Dependencies.respond_to?(:load_paths)
921
922
  end
922
923
 
923
924
  module ActiveLdap
924
- VERSION = "0.9.0"
925
+ VERSION = "0.10.0"
925
926
  end
926
927
 
927
928
  if RUBY_PLATFORM.match('linux')
@@ -939,6 +940,9 @@ require 'active_ldap/get_text'
939
940
 
940
941
  require 'active_ldap/base'
941
942
 
943
+ require 'active_ldap/distinguished_name'
944
+ require 'active_ldap/ldif'
945
+
942
946
  require 'active_ldap/associations'
943
947
  require 'active_ldap/attributes'
944
948
  require 'active_ldap/configuration'
@@ -949,7 +953,6 @@ require 'active_ldap/human_readable'
949
953
 
950
954
  require 'active_ldap/acts/tree'
951
955
 
952
- require 'active_ldap/distinguished_name'
953
956
  require 'active_ldap/populate'
954
957
  require 'active_ldap/escape'
955
958
  require 'active_ldap/helper'
@@ -974,7 +977,7 @@ ActiveLdap::Base.class_eval do
974
977
  end
975
978
 
976
979
  unless defined?(ACTIVE_LDAP_CONNECTION_ADAPTERS)
977
- ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap net_ldap)
980
+ ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap net_ldap jndi)
978
981
  end
979
982
 
980
983
  ACTIVE_LDAP_CONNECTION_ADAPTERS.each do |adapter|
@@ -1,4 +1,7 @@
1
+ require 'benchmark'
2
+
1
3
  require 'active_ldap/schema'
4
+ require 'active_ldap/entry_attribute'
2
5
  require 'active_ldap/ldap_error'
3
6
 
4
7
  module ActiveLdap
@@ -13,9 +16,15 @@ module ActiveLdap
13
16
  :sasl_mechanisms, :sasl_quiet,
14
17
  :allow_anonymous, :store_password,
15
18
  :scope]
19
+
20
+ @@row_even = true
21
+
22
+ attr_reader :runtime
16
23
  def initialize(configuration={})
24
+ @runtime = 0
17
25
  @connection = nil
18
26
  @disconnected = false
27
+ @entry_attributes = {}
19
28
  @configuration = configuration.dup
20
29
  @logger = @configuration.delete(:logger)
21
30
  @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
@@ -24,12 +33,17 @@ module ActiveLdap
24
33
  end
25
34
  end
26
35
 
36
+ def reset_runtime
37
+ runtime, @runtime = @runtime, 0
38
+ runtime
39
+ end
40
+
27
41
  def connect(options={})
28
42
  host = options[:host] || @host
29
43
  port = options[:port] || @port
30
44
  method = ensure_method(options[:method] || @method)
31
45
  @disconnected = false
32
- @connection = yield(host, port, method)
46
+ @connection, @uri, @with_start_tls = yield(host, port, method)
33
47
  prepare_connection(options)
34
48
  bind(options)
35
49
  end
@@ -37,7 +51,7 @@ module ActiveLdap
37
51
  def disconnect!(options={})
38
52
  return if @connection.nil?
39
53
  unbind(options)
40
- @connection = nil
54
+ @connection = @uri = @with_start_tls = nil
41
55
  end
42
56
 
43
57
  def rebind(options={})
@@ -53,20 +67,21 @@ module ActiveLdap
53
67
  else
54
68
  allow_anonymous = @allow_anonymous
55
69
  end
70
+ options = options.merge(:allow_anonymous => allow_anonymous)
56
71
 
57
72
  # Rough bind loop:
58
73
  # Attempt 1: SASL if available
59
74
  # Attempt 2: SIMPLE with credentials if password block
60
75
  # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
61
76
  if try_sasl and sasl_bind(bind_dn, options)
62
- @logger.info {_('Bound by SASL as %s') % bind_dn}
77
+ @logger.info {_('Bound to %s by SASL as %s') % [target, bind_dn]}
63
78
  elsif simple_bind(bind_dn, options)
64
- @logger.info {_('Bound by simple as %s') % bind_dn}
79
+ @logger.info {_('Bound to %s by simple as %s') % [target, bind_dn]}
65
80
  elsif allow_anonymous and bind_as_anonymous(options)
66
- @logger.info {_('Bound as anonymous')}
81
+ @logger.info {_('Bound to %s as anonymous') % target}
67
82
  else
68
83
  message = yield if block_given?
69
- message ||= _('All authentication methods exhausted.')
84
+ message ||= _('All authentication methods for %s exhausted.') % target
70
85
  raise AuthenticationError, message
71
86
  end
72
87
 
@@ -109,12 +124,9 @@ module ActiveLdap
109
124
  end
110
125
  end
111
126
 
112
- def load(ldifs, options={})
113
- operation(options) do
114
- ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
115
- yield(ldif)
116
- end
117
- end
127
+ def entry_attribute(object_classes)
128
+ @entry_attributes[object_classes.uniq.sort] ||=
129
+ EntryAttribute.new(schema, object_classes)
118
130
  end
119
131
 
120
132
  def search(options={})
@@ -195,26 +207,41 @@ module ActiveLdap
195
207
  end
196
208
  end
197
209
 
210
+ def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
211
+ operation(options) do
212
+ yield(dn, new_rdn, delete_old_rdn, new_superior)
213
+ end
214
+ end
215
+
216
+ def log_info(name, runtime, info=nil)
217
+ return unless @logger
218
+ return unless @logger.debug?
219
+ message = "LDAP: #{name} (#{'%f' % runtime})"
220
+ @logger.debug(format_log_entry(message, info))
221
+ end
222
+
198
223
  private
199
224
  def prepare_connection(options)
200
225
  end
201
226
 
202
227
  def operation(options)
203
228
  retried = false
229
+ options = options.dup
230
+ options[:try_reconnect] = true unless options.has_key?(:try_reconnect)
231
+ try_reconnect = false
204
232
  begin
205
- reconnect_if_need
206
- try_reconnect = !options.has_key?(:try_reconnect) ||
207
- options[:try_reconnect]
233
+ reconnect_if_need(options)
234
+ try_reconnect = options[:try_reconnect]
208
235
  with_timeout(try_reconnect, options) do
209
236
  yield
210
237
  end
211
- rescue Errno::EPIPE
212
- if retried or !try_reconnect
213
- raise
214
- else
238
+ rescue Errno::EPIPE, ConnectionError
239
+ if try_reconnect and !retried
215
240
  retried = true
216
241
  @disconnected = true
217
242
  retry
243
+ else
244
+ raise
218
245
  end
219
246
  end
220
247
  end
@@ -253,7 +280,7 @@ module ActiveLdap
253
280
  Timeout.alarm(@timeout, &block)
254
281
  rescue Timeout::Error => e
255
282
  @logger.error {_('Requested action timed out.')}
256
- retry if try_reconnect and @retry_on_timeout and reconnect(options)
283
+ retry if @retry_on_timeout and try_reconnect and reconnect(options)
257
284
  @logger.error {e.message}
258
285
  raise TimeoutError, e.message
259
286
  end
@@ -290,6 +317,16 @@ module ActiveLdap
290
317
  passwd = password(bind_dn, options)
291
318
  return false unless passwd
292
319
 
320
+ if passwd.empty?
321
+ if options[:allow_anonymous]
322
+ @logger.info {_("Skip simple bind with empty password.")}
323
+ return false
324
+ else
325
+ raise AuthenticationError,
326
+ _("Can't use empty password for simple bind.")
327
+ end
328
+ end
329
+
293
330
  begin
294
331
  operation(options) do
295
332
  yield(bind_dn, passwd)
@@ -318,24 +355,7 @@ module ActiveLdap
318
355
  construct_filter(components, operator)
319
356
  else
320
357
  operator, components = normalize_array_filter(filter, operator)
321
-
322
- components = components.collect do |component|
323
- if component.is_a?(Array) and component.size == 2
324
- key, value = component
325
- if filter_logical_operator?(key)
326
- parse_filter(component)
327
- elsif value.is_a?(Hash)
328
- parse_filter(value, key)
329
- else
330
- construct_component(key, value, operator)
331
- end
332
- elsif component.is_a?(Symbol)
333
- assert_filter_logical_operator(component)
334
- nil
335
- else
336
- parse_filter(component, operator)
337
- end
338
- end
358
+ components = construct_components(components, operator)
339
359
  construct_filter(components, operator)
340
360
  end
341
361
  end
@@ -358,6 +378,7 @@ module ActiveLdap
358
378
  operator = filter_operator
359
379
  else
360
380
  components.unshift(filter_operator)
381
+ components = [components] unless filter_operator.is_a?(Array)
361
382
  end
362
383
  [operator, components]
363
384
  end
@@ -370,22 +391,53 @@ module ActiveLdap
370
391
  options = value[0]
371
392
  value = value[1]
372
393
  when "=", "~=", "<=", "=>"
373
- options[:operator] = value[1]
374
- value = value[1]
394
+ options[:operator] = value[0]
395
+ if value.size > 2
396
+ value = value[1..-1]
397
+ else
398
+ value = value[1]
399
+ end
375
400
  end
376
401
  end
377
402
  [value, options]
378
403
  end
379
404
 
405
+ def construct_components(components, operator)
406
+ components.collect do |component|
407
+ if component.is_a?(Array)
408
+ if filter_logical_operator?(component[0])
409
+ parse_filter(component)
410
+ elsif component.size == 2
411
+ key, value = component
412
+ if value.is_a?(Hash)
413
+ parse_filter(value, key)
414
+ else
415
+ construct_component(key, value, operator)
416
+ end
417
+ else
418
+ construct_component(component[0], component[1..-1], operator)
419
+ end
420
+ elsif component.is_a?(Symbol)
421
+ assert_filter_logical_operator(component)
422
+ nil
423
+ else
424
+ parse_filter(component, operator)
425
+ end
426
+ end
427
+ end
428
+
380
429
  def construct_component(key, value, operator=nil)
381
430
  value, options = extract_filter_value_options(value)
431
+ comparison_operator = options[:operator] || "="
382
432
  if collection?(value)
433
+ return nil if value.empty?
434
+ operator, value = normalize_array_filter(value, operator)
383
435
  values = []
384
436
  value.each do |val|
385
437
  if collection?(val)
386
- values.concat(val.collect {|v| [key, v]})
438
+ values.concat(val.collect {|v| [key, comparison_operator, v]})
387
439
  else
388
- values << [key, val]
440
+ values << [key, comparison_operator, val]
389
441
  end
390
442
  end
391
443
  values[0] = values[0][1] if filter_logical_operator?(values[0][1])
@@ -394,7 +446,7 @@ module ActiveLdap
394
446
  [
395
447
  "(",
396
448
  escape_filter_key(key),
397
- options[:operator] || "=",
449
+ comparison_operator,
398
450
  escape_filter_value(value, options),
399
451
  ")"
400
452
  ].join
@@ -526,6 +578,70 @@ module ActiveLdap
526
578
  return [] if dse.nil?
527
579
  dse[key] || dse[key.downcase] || []
528
580
  end
581
+
582
+ def root_dse(attrs, options={})
583
+ search(:base => "",
584
+ :scope => :base,
585
+ :attributes => attrs).collect do |dn, attributes|
586
+ attributes
587
+ end
588
+ end
589
+
590
+ def construct_uri(host, port, ssl)
591
+ protocol = ssl ? "ldaps" : "ldap"
592
+ URI.parse("#{protocol}://#{host}:#{port}").to_s
593
+ end
594
+
595
+ def target
596
+ return nil if @uri.nil?
597
+ if @with_start_tls
598
+ "#{@uri}(StartTLS)"
599
+ else
600
+ @uri
601
+ end
602
+ end
603
+
604
+ def log(name, info=nil)
605
+ if block_given?
606
+ if @logger and @logger.debug?
607
+ result = nil
608
+ runtime = Benchmark.realtime {result = yield}
609
+ @runtime += runtime
610
+ log_info(name, runtime, info)
611
+ result
612
+ else
613
+ yield
614
+ end
615
+ else
616
+ log_info(name, info, 0)
617
+ nil
618
+ end
619
+ rescue Exception
620
+ log_info("#{name}: FAILED", 0,
621
+ (info || {}).merge(:error => $!.class.name,
622
+ :error_message => $!.message))
623
+ raise
624
+ end
625
+
626
+ def format_log_entry(message, info=nil)
627
+ if ActiveLdap::Base.colorize_logging
628
+ if @@row_even
629
+ message_color, dump_color = "4;36;1", "0;1"
630
+ else
631
+ @@row_even = true
632
+ message_color, dump_color = "4;35;1", "0"
633
+ end
634
+ @@row_even = !@@row_even
635
+
636
+ log_entry = " \e[#{message_color}m#{message}\e[0m"
637
+ log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info
638
+ log_entry
639
+ else
640
+ log_entry = message
641
+ log_entry += ": #{info.inspect}" if info
642
+ log_entry
643
+ end
644
+ end
529
645
  end
530
646
  end
531
647
  end
@@ -0,0 +1,175 @@
1
+ require 'active_ldap/adapter/base'
2
+
3
+ module ActiveLdap
4
+ module Adapter
5
+ class Base
6
+ class << self
7
+ def jndi_connection(options)
8
+ require 'active_ldap/adapter/jndi_connection'
9
+ Jndi.new(options)
10
+ end
11
+ end
12
+ end
13
+
14
+ class Jndi < Base
15
+ METHOD = {
16
+ :ssl => :ssl,
17
+ :tls => :start_tls,
18
+ :plain => nil,
19
+ }
20
+
21
+ def connect(options={})
22
+ super do |host, port, method|
23
+ uri = construct_uri(host, port, method == :ssl)
24
+ with_start_tls = method == :start_tls
25
+ info = {:uri => uri, :with_start_tls => with_start_tls}
26
+ [log("connect", info) {JndiConnection.new(host, port, method)},
27
+ uri, with_start_tls]
28
+ end
29
+ end
30
+
31
+ def unbind(options={})
32
+ return unless bound?
33
+ operation(options) do
34
+ execute(:unbind)
35
+ end
36
+ end
37
+
38
+ def bind_as_anonymous(options={})
39
+ super do
40
+ execute(:bind_as_anonymous, :name => "bind: anonymous")
41
+ end
42
+ end
43
+
44
+ def bound?
45
+ connecting? and @connection.bound?
46
+ end
47
+
48
+ def search(options={}, &block)
49
+ super(options) do |base, scope, filter, attrs, limit, callback|
50
+ info = {
51
+ :base => base, :scope => scope_name(scope), :filter => filter,
52
+ :attributes => attrs,
53
+ }
54
+ execute(:search, info,
55
+ base, scope, filter, attrs, limit, callback, &block)
56
+ end
57
+ end
58
+
59
+ def delete(targets, options={})
60
+ super do |target|
61
+ execute(:delete, {:dn => target}, target)
62
+ end
63
+ end
64
+
65
+ def add(dn, entries, options={})
66
+ super do |dn, entries|
67
+ info = {:dn => dn, :attributes => entries}
68
+ execute(:add, info, dn, parse_entries(entries))
69
+ end
70
+ end
71
+
72
+ def modify(dn, entries, options={})
73
+ super do |dn, entries|
74
+ info = {:dn => dn, :attributes => entries}
75
+ execute(:modify, info, dn, parse_entries(entries))
76
+ end
77
+ end
78
+
79
+ def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
80
+ super do |dn, new_rdn, delete_old_rdn, new_superior|
81
+ info = {
82
+ :name => "modify: RDN", :dn => dn, :new_rdn => new_rdn,
83
+ :delete_old_rdn => delete_old_rdn,
84
+ }
85
+ execute(:modify_rdn, dn, new_rdn, delete_old_rdn)
86
+ end
87
+ end
88
+
89
+ private
90
+ def execute(method, info=nil, *args, &block)
91
+ name = (info || {}).delete(:name) || method
92
+ log(name, info) {@connection.send(method, *args, &block)}
93
+ rescue JndiConnection::NamingException
94
+ if /\[LDAP: error code (\d+) - ([^\]]+)\]/ =~ $!.to_s
95
+ message = $2
96
+ klass = LdapError::ERRORS[Integer($1)]
97
+ klass ||= ActiveLdap::LdapError
98
+ raise klass, message
99
+ end
100
+ raise
101
+ end
102
+
103
+ def ensure_method(method)
104
+ method ||= "plain"
105
+ normalized_method = method.to_s.downcase.to_sym
106
+ return METHOD[normalized_method] if METHOD.has_key?(normalized_method)
107
+
108
+ available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ")
109
+ format = _("%s is not one of the available connect methods: %s")
110
+ raise ConfigurationError, format % [method.inspect, available_methods]
111
+ end
112
+
113
+ def ensure_scope(scope)
114
+ scope_map = {
115
+ :base => 0,
116
+ :one => 1,
117
+ :sub => 2,
118
+ }
119
+ value = scope_map[scope || :sub]
120
+ if value.nil?
121
+ available_scopes = scope_map.keys.inspect
122
+ format = _("%s is not one of the available LDAP scope: %s")
123
+ raise ArgumentError, format % [scope.inspect, available_scopes]
124
+ end
125
+ value
126
+ end
127
+
128
+ def scope_name(scope)
129
+ {
130
+ 0 => :base,
131
+ 1 => :one,
132
+ 2 => :sub,
133
+ }[scope]
134
+ end
135
+
136
+ def sasl_bind(bind_dn, options={})
137
+ super do |bind_dn, mechanism, quiet|
138
+ info = {:name => "bind: SASL", :dn => bind_dn, :mechanism => mechanism}
139
+ execute(:sasl_bind, info, bind_dn, mechanism, quiet)
140
+ end
141
+ end
142
+
143
+ def simple_bind(bind_dn, options={})
144
+ super do |bind_dn, passwd|
145
+ info = {:name => "bind", :dn => bind_dn}
146
+ execute(:simple_bind, info, bind_dn, passwd)
147
+ end
148
+ end
149
+
150
+ def parse_entries(entries)
151
+ result = []
152
+ entries.each do |type, key, attributes|
153
+ mod_type = ensure_mod_type(type)
154
+ binary = schema.attribute(key).binary?
155
+ attributes.each do |name, values|
156
+ result << JndiConnection::ModifyRecord.new(mod_type, name,
157
+ values, binary)
158
+ end
159
+ end
160
+ result
161
+ end
162
+
163
+ def ensure_mod_type(type)
164
+ case type
165
+ when :replace, :add
166
+ type
167
+ when :delete
168
+ :remove
169
+ else
170
+ raise ArgumentError, _("unknown type: %s") % type
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end