powerhome-activeldap 3.2.3

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 (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,1410 @@
1
+ # -*- coding: utf-8 -*-
2
+ # === activeldap - an OO-interface to LDAP objects inspired by ActiveRecord
3
+ # Author: Will Drewry <will@alum.bu.edu>
4
+ # License: See LICENSE and COPYING
5
+ # Copyright 2004-2006 Will Drewry <will@alum.bu.edu>
6
+ # Some portions Copyright 2006 Google Inc
7
+ #
8
+ # == Summary
9
+ # ActiveLdap lets you read and update LDAP entries in a completely object
10
+ # oriented fashion, even handling attributes with multiple names seamlessly.
11
+ # It was inspired by ActiveRecord so extending it to deal with custom
12
+ # LDAP schemas is as effortless as knowing the 'ou' of the objects, and the
13
+ # primary key. (fix this up some)
14
+ #
15
+ # == Example
16
+ # irb> require 'active_ldap'
17
+ # > true
18
+ # irb> user = ActiveLdap::User.new("drewry")
19
+ # > #<ActiveLdap::User:0x402e...
20
+ # irb> user.cn
21
+ # > "foo"
22
+ # irb> user.common_name
23
+ # > "foo"
24
+ # irb> user.cn = "Will Drewry"
25
+ # > "Will Drewry"
26
+ # irb> user.cn
27
+ # > "Will Drewry"
28
+ # irb> user.save
29
+ #
30
+ #
31
+
32
+ require 'English'
33
+ require 'thread'
34
+ require 'erb'
35
+
36
+ module ActiveLdap
37
+ # OO-interface to LDAP assuming pam/nss_ldap-style organization with
38
+ # Active specifics
39
+ # Each subclass does a ldapsearch for the matching entry.
40
+ # If no exact match, raise an error.
41
+ # If match, change all LDAP attributes in accessor attributes on the object.
42
+ # -- these are ACTUALLY populated from schema - see active_ldap/schema.rb
43
+ # example
44
+ # -- extract objectClasses from match and populate
45
+ # Multiple entries become lists.
46
+ # If this isn't read-only then lists become multiple entries, etc.
47
+
48
+ class << self
49
+ include GetTextSupport
50
+ def const_missing(id)
51
+ case id
52
+ when :ConnectionNotEstablished
53
+ message =
54
+ _("ActiveLdap::ConnectionNotEstablished has been deprecated " \
55
+ "since 1.1.0. " \
56
+ "Please use ActiveLdap::ConnectionNotSetup instead.")
57
+ ActiveSupport::Deprecation.warn(message)
58
+ const_set("ConnectionNotEstablished", ConnectionNotSetup)
59
+ ConnectionNotEstablished
60
+ else
61
+ super
62
+ end
63
+ end
64
+ end
65
+
66
+ class Error < StandardError
67
+ include GetTextSupport
68
+ end
69
+
70
+ # ConfigurationError
71
+ #
72
+ # An exception raised when there is a problem with Base.connect arguments
73
+ class ConfigurationError < Error
74
+ end
75
+
76
+ # DeleteError
77
+ #
78
+ # An exception raised when an ActiveLdap delete action fails
79
+ class DeleteError < Error
80
+ end
81
+
82
+ # SaveError
83
+ #
84
+ # An exception raised when an ActiveLdap save action failes
85
+ class SaveError < Error
86
+ end
87
+
88
+ # AuthenticationError
89
+ #
90
+ # An exception raised when user authentication fails
91
+ class AuthenticationError < Error
92
+ end
93
+
94
+ # ConnectionError
95
+ #
96
+ # An exception raised when the LDAP conenction fails
97
+ class ConnectionError < Error
98
+ end
99
+
100
+ # ObjectClassError
101
+ #
102
+ # An exception raised when an objectClass is not defined in the schema
103
+ class ObjectClassError < Error
104
+ end
105
+
106
+ # AttributeAssignmentError
107
+ #
108
+ # An exception raised when there is an issue assigning a value to
109
+ # an attribute
110
+ class AttributeAssignmentError < Error
111
+ end
112
+
113
+ # TimeoutError
114
+ #
115
+ # An exception raised when a connection action fails due to a timeout
116
+ class TimeoutError < Error
117
+ end
118
+
119
+ class EntryNotFound < Error
120
+ end
121
+
122
+ class EntryAlreadyExist < Error
123
+ end
124
+
125
+ class StrongAuthenticationRequired < Error
126
+ end
127
+
128
+ class DistinguishedNameInputInvalid < Error
129
+ attr_reader :input
130
+ def initialize(input=nil)
131
+ @input = input
132
+ super(_("invalid distinguished name (DN) to parse: %s") % @input.inspect)
133
+ end
134
+ end
135
+
136
+ class DistinguishedNameInvalid < Error
137
+ attr_reader :dn, :reason
138
+ def initialize(dn, reason=nil)
139
+ @dn = dn
140
+ @reason = reason
141
+ if @reason
142
+ message = _("%s is invalid distinguished name (DN): %s") % [@dn, @reason]
143
+ else
144
+ message = _("%s is invalid distinguished name (DN)") % @dn
145
+ end
146
+ super(message)
147
+ end
148
+ end
149
+
150
+ class DistinguishedNameNotSetError < Error
151
+ end
152
+
153
+ class LdifInvalid < Error
154
+ attr_reader :ldif, :reason, :line, :column, :nearest
155
+ def initialize(ldif, reason=nil, line=nil, column=nil)
156
+ @ldif = ldif
157
+ @reason = reason
158
+ @line = line
159
+ @column = column
160
+ @nearest = nil
161
+ if @reason
162
+ message = _("invalid LDIF: %s:") % @reason
163
+ else
164
+ message = _("invalid LDIF:")
165
+ end
166
+ if @line and @column
167
+ @nearest = detect_nearest(@line, @column)
168
+ snippet = generate_snippet
169
+ message << "\n#{snippet}\n"
170
+ end
171
+ super("#{message}\n#{numbered_ldif}")
172
+ end
173
+
174
+ NEAREST_MARK = "|@|"
175
+ private
176
+ def detect_nearest(line, column)
177
+ lines = Compatible.string_to_lines(@ldif).to_a
178
+ nearest = lines[line - 1] || ""
179
+ if column - 1 == nearest.size # for JRuby 1.0.2 :<
180
+ nearest << NEAREST_MARK
181
+ else
182
+ nearest[column - 1, 0] = NEAREST_MARK
183
+ end
184
+ if nearest == NEAREST_MARK
185
+ nearest = "#{lines[line - 2]}#{nearest}"
186
+ end
187
+ nearest
188
+ end
189
+
190
+ def generate_snippet
191
+ nearest = @nearest.chomp
192
+ column_column = ":#{@column}"
193
+ target_position_info = "#{@line}#{column_column}: "
194
+ if /\n/ =~ nearest
195
+ snippet = "%#{Math.log10(@line).truncate}d" % (@line - 1)
196
+ snippet << " " * column_column.size
197
+ snippet << ": "
198
+ snippet << nearest.gsub(/\n/, "\n#{target_position_info}")
199
+ else
200
+ snippet = "#{target_position_info}#{nearest}"
201
+ end
202
+ snippet
203
+ end
204
+
205
+ def numbered_ldif
206
+ return @ldif if @ldif.blank?
207
+ lines = Compatible.string_to_lines(@ldif)
208
+ format = "%#{Math.log10(lines.size).truncate + 1}d: %s"
209
+ i = 0
210
+ lines.collect do |line|
211
+ i += 1
212
+ format % [i, line]
213
+ end.join
214
+ end
215
+ end
216
+
217
+ class EntryNotSaved < Error
218
+ end
219
+
220
+ class RequiredObjectClassMissed < Error
221
+ end
222
+
223
+ class RequiredAttributeMissed < Error
224
+ end
225
+
226
+ class EntryInvalid < Error
227
+ attr_reader :entry
228
+ def initialize(entry)
229
+ @entry = entry
230
+ errors = @entry.errors.full_messages.join(", ")
231
+ super(errors)
232
+ end
233
+ end
234
+
235
+ class OperationNotPermitted < Error
236
+ end
237
+
238
+ class ConnectionNotSetup < Error
239
+ end
240
+
241
+ class AdapterNotSpecified < Error
242
+ end
243
+
244
+ class AdapterNotFound < Error
245
+ attr_reader :adapter
246
+ def initialize(adapter)
247
+ @adapter = adapter
248
+ super(_("LDAP configuration specifies nonexistent %s adapter") % adapter)
249
+ end
250
+ end
251
+
252
+ class UnknownAttribute < Error
253
+ attr_reader :name
254
+ def initialize(name)
255
+ @name = name
256
+ super(_("%s is unknown attribute") % @name)
257
+ end
258
+ end
259
+
260
+ class AttributeValueInvalid < Error
261
+ attr_reader :attribute, :value
262
+ def initialize(attribute, value, message)
263
+ @attribute = attribute
264
+ @value = value
265
+ super(message)
266
+ end
267
+ end
268
+
269
+ class NotImplemented < Error
270
+ attr_reader :target
271
+ def initialize(target)
272
+ @target = target
273
+ super(_("not implemented: %s") % @target)
274
+ end
275
+ end
276
+
277
+ # Base
278
+ #
279
+ # Base is the primary class which contains all of the core
280
+ # ActiveLdap functionality. It is meant to only ever be subclassed
281
+ # by extension classes.
282
+ class Base
283
+ include GetTextSupport
284
+ public :gettext
285
+ public :_
286
+
287
+ if Object.const_defined?(:Reloadable)
288
+ if Reloadable.const_defined?(:Deprecated)
289
+ include Reloadable::Deprecated
290
+ else
291
+ include Reloadable::Subclasses
292
+ end
293
+ end
294
+
295
+ cattr_accessor :colorize_logging, :instance_writer => false
296
+ @@colorize_logging = true
297
+
298
+ VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :scope,
299
+ :classes, :recommended_classes,
300
+ :excluded_classes, :sort_by, :order]
301
+
302
+ cattr_accessor :logger
303
+ cattr_accessor :configurations
304
+ @@configurations = {}
305
+
306
+ def self.class_local_attr_accessor(search_ancestors, *syms)
307
+ syms.flatten.each do |sym|
308
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
309
+ def self.#{sym}(search_superclasses=#{search_ancestors})
310
+ @#{sym} ||= nil
311
+ return @#{sym} if @#{sym}
312
+ if search_superclasses
313
+ target = superclass
314
+ value = nil
315
+ loop do
316
+ break nil unless target.respond_to?(:#{sym})
317
+ value = target.#{sym}
318
+ break if value
319
+ target = target.superclass
320
+ end
321
+ value
322
+ else
323
+ nil
324
+ end
325
+ end
326
+ def #{sym}; self.class.#{sym}; end
327
+ def self.#{sym}=(value); @#{sym} = value; end
328
+ EOS
329
+ end
330
+ end
331
+
332
+ class_local_attr_accessor false, :inheritable_prefix, :inheritable_base
333
+ class_local_attr_accessor true, :dn_attribute, :scope, :sort_by, :order
334
+ class_local_attr_accessor true, :required_classes, :recommended_classes
335
+ class_local_attr_accessor true, :excluded_classes
336
+
337
+ class << self
338
+ # Hide new in Base
339
+ private :new
340
+
341
+ def inherited(sub_class)
342
+ super
343
+ sub_class.module_eval do
344
+ include GetTextSupport
345
+ end
346
+ end
347
+
348
+ # Set LDAP connection configuration up. It doesn't connect
349
+ # and bind to LDAP server. A connection to LDAP server is
350
+ # created when it's needed.
351
+ #
352
+ # == +config+
353
+ # +config+ must be a hash that may contain any of the following fields:
354
+ # :password_block, :logger, :host, :port, :base, :bind_dn,
355
+ # :try_sasl, :allow_anonymous
356
+ # :bind_dn specifies the DN to bind with.
357
+ # :password_block specifies a Proc object that will yield a String to
358
+ # be used as the password when called.
359
+ # :logger specifies a logger object (Logger, Log4r::Logger and s on)
360
+ # :host sets the LDAP server hostname
361
+ # :port sets the LDAP server port
362
+ # :base overwrites Base.base - this affects EVERYTHING
363
+ # :try_sasl indicates that a SASL bind should be attempted when binding
364
+ # to the server (default: false)
365
+ # :sasl_mechanisms is an array of SASL mechanism to try
366
+ # (default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
367
+ # :allow_anonymous indicates that a true anonymous bind is allowed when
368
+ # trying to bind to the server (default: true)
369
+ # :retries - indicates the number of attempts to reconnect that will be
370
+ # undertaken when a stale connection occurs. -1 means infinite.
371
+ # :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
372
+ # :method - whether to use :ssl, :tls, or :plain (unencrypted)
373
+ # :retry_wait - seconds to wait before retrying a connection
374
+ # :scope - dictates how to find objects. ONELEVEL by default to
375
+ # avoid dn_attr collisions across OUs. Think before changing.
376
+ # :timeout - time in seconds - defaults to disabled. This CAN interrupt
377
+ # search() requests. Be warned.
378
+ # :retry_on_timeout - whether to reconnect when timeouts occur. Defaults
379
+ # to true
380
+ # See lib/active_ldap/configuration.rb for defaults for each option
381
+ def setup_connection(config=nil)
382
+ super
383
+ ensure_logger
384
+ nil
385
+ end
386
+
387
+ # establish_connection is deprecated since 1.1.0. Please use
388
+ # setup_connection() instead.
389
+ def establish_connection(config=nil)
390
+ message =
391
+ _("ActiveLdap::Base.establish_connection has been deprecated " \
392
+ "since 1.1.0. " \
393
+ "Please use ActiveLdap::Base.setup_connection instead.")
394
+ ActiveSupport::Deprecation.warn(message)
395
+ setup_connection(config)
396
+ end
397
+
398
+ def create(attributes=nil, &block)
399
+ if attributes.is_a?(Array)
400
+ attributes.collect {|attrs| create(attrs, &block)}
401
+ else
402
+ object = new(attributes, &block)
403
+ object.save
404
+ object
405
+ end
406
+ end
407
+
408
+ # This class function is used to setup all mappings between the subclass
409
+ # and ldap for use in activeldap
410
+ #
411
+ # Example:
412
+ # ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
413
+ # :classes => ['top', 'posixAccount'],
414
+ # :scope => :sub
415
+ def ldap_mapping(options={})
416
+ options = options.symbolize_keys
417
+ validate_ldap_mapping_options(options)
418
+
419
+ self.dn_attribute = options[:dn_attribute] || default_dn_attribute
420
+ self.dn_attribute = dn_attribute.to_s if dn_attribute.is_a?(Symbol)
421
+ self.prefix = options[:prefix] || default_prefix
422
+ self.scope = options[:scope]
423
+ self.required_classes = options[:classes]
424
+ self.recommended_classes = options[:recommended_classes]
425
+ self.excluded_classes = options[:excluded_classes]
426
+ self.sort_by = options[:sort_by]
427
+ self.order = options[:order]
428
+
429
+ public_class_method :new
430
+ end
431
+
432
+ # Base.base
433
+ #
434
+ # This method when included into Base provides
435
+ # an inheritable, overwritable configuration setting
436
+ #
437
+ # This should be a string with the base of the
438
+ # ldap server such as 'dc=example,dc=com', and
439
+ # it should be overwritten by including
440
+ # configuration.rb into this class.
441
+ # When subclassing, the specified prefix will be concatenated.
442
+ def base
443
+ @base ||= compute_base
444
+ end
445
+ alias_method :parsed_base, :base # for backward compatibility
446
+
447
+ def base=(value)
448
+ self.inheritable_base = value
449
+ @base = nil
450
+ end
451
+
452
+ def prefix
453
+ @prefix ||= inheritable_prefix and DN.parse(inheritable_prefix)
454
+ end
455
+
456
+ def prefix=(value)
457
+ self.inheritable_prefix = value
458
+ @prefix = nil
459
+ @base = nil
460
+ end
461
+
462
+ alias_method :scope_without_validation=, :scope=
463
+ def scope=(scope)
464
+ validate_scope(scope)
465
+ self.scope_without_validation = scope
466
+ end
467
+
468
+ def validate_scope(scope)
469
+ scope = scope.to_sym if scope.is_a?(String)
470
+ return if scope.nil? or scope.is_a?(Symbol)
471
+ raise ConfigurationError,
472
+ _("scope '%s' must be a Symbol") % scope.inspect
473
+ end
474
+
475
+ def base_class
476
+ if self == Base or superclass == Base
477
+ self
478
+ else
479
+ superclass.base_class
480
+ end
481
+ end
482
+
483
+ def default_search_attribute
484
+ dn_attribute
485
+ end
486
+
487
+ def inspect
488
+ if self == Base
489
+ super
490
+ elsif abstract_class?
491
+ "#{super}(abstract)"
492
+ else
493
+ detail = nil
494
+ begin
495
+ must = []
496
+ may = []
497
+ class_names = classes.collect do |object_class|
498
+ must.concat(object_class.must)
499
+ may.concat(object_class.may)
500
+ object_class.name
501
+ end
502
+ detail = ["objectClass:<#{class_names.join(', ')}>",
503
+ "must:<#{inspect_attributes(must)}>",
504
+ "may:<#{inspect_attributes(may)}>"].join(", ")
505
+ rescue ActiveLdap::ConnectionNotSetup
506
+ detail = "not-connected"
507
+ rescue ActiveLdap::Error
508
+ detail = "connection-failure"
509
+ end
510
+ "#{super}(#{detail})"
511
+ end
512
+ end
513
+
514
+ attr_accessor :abstract_class
515
+ def abstract_class?
516
+ defined?(@abstract_class) && @abstract_class
517
+ end
518
+
519
+ def class_of_active_ldap_descendant(klass)
520
+ if klass.superclass == Base or klass.superclass.abstract_class?
521
+ klass
522
+ elsif klass.superclass.nil?
523
+ raise Error, _("%s doesn't belong in a hierarchy descending " \
524
+ "from ActiveLdap") % (name || to_s)
525
+ else
526
+ class_of_active_ldap_descendant(klass.superclass)
527
+ end
528
+ end
529
+
530
+ def self_and_descendants_from_active_ldap
531
+ klass = self
532
+ classes = [klass]
533
+ while klass != klass.base_class
534
+ classes << klass = klass.superclass
535
+ end
536
+ classes
537
+ rescue
538
+ [self]
539
+ end
540
+
541
+ def human_name(options={})
542
+ defaults = self_and_descendants_from_active_ldap.collect do |klass|
543
+ if klass.name.blank?
544
+ nil
545
+ else
546
+ :"#{klass.name.underscore}"
547
+ end
548
+ end
549
+ defaults << name.humanize
550
+ defaults = defaults.compact
551
+ defaults.first || name || to_s
552
+ end
553
+
554
+ private
555
+ def inspect_attributes(attributes)
556
+ inspected_attribute_names = {}
557
+ attributes.collect do |attribute|
558
+ if inspected_attribute_names.has_key?(attribute.name)
559
+ nil
560
+ else
561
+ inspected_attribute_names[attribute.name] = true
562
+ inspect_attribute(attribute)
563
+ end
564
+ end.compact.join(', ')
565
+ end
566
+
567
+ def inspect_attribute(attribute)
568
+ syntax = attribute.syntax
569
+ result = "#{attribute.name}"
570
+ if syntax and !syntax.description.blank?
571
+ result << ": #{syntax.description}"
572
+ end
573
+ properties = []
574
+ properties << "read-only" if attribute.read_only?
575
+ properties << "binary" if attribute.binary?
576
+ properties << "binary-required" if attribute.binary_required?
577
+ result << "(#{properties.join(', ')})" unless properties.empty?
578
+ result
579
+ end
580
+
581
+ def validate_ldap_mapping_options(options)
582
+ options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
583
+ end
584
+
585
+ def ensure_logger
586
+ @@logger ||= configuration[:logger]
587
+ # Setup default logger to console
588
+ if @@logger.nil?
589
+ require 'logger'
590
+ @@logger = Logger.new(STDERR)
591
+ @@logger.progname = 'ActiveLdap'
592
+ @@logger.level = Logger::ERROR
593
+ end
594
+ configuration[:logger] ||= @@logger
595
+ end
596
+
597
+ def instantiate(args)
598
+ dn, attributes, options = args
599
+ options ||= {}
600
+ if self.class == Class
601
+ klass = self.ancestors[0].to_s.split(':').last
602
+ real_klass = self.ancestors[0]
603
+ else
604
+ klass = self.class.to_s.split(':').last
605
+ real_klass = self.class
606
+ end
607
+
608
+ obj = real_klass.allocate
609
+ conn = options[:connection] || connection
610
+ obj.connection = conn if conn != connection
611
+ obj.instance_eval do
612
+ initialize_by_ldap_data(dn, attributes)
613
+ end
614
+ obj
615
+ end
616
+
617
+ def default_dn_attribute
618
+ dn_attribute = nil
619
+ parent_class = ancestors[1]
620
+ if parent_class.respond_to?(:dn_attribute)
621
+ dn_attribute = parent_class.dn_attribute
622
+ end
623
+ dn_attribute || "cn"
624
+ end
625
+
626
+ def default_prefix
627
+ if name.blank?
628
+ nil
629
+ else
630
+ "ou=#{name.demodulize.pluralize}"
631
+ end
632
+ end
633
+
634
+ def compute_base
635
+ _base = inheritable_base
636
+ _base = configuration[:base] if _base.nil? and configuration
637
+ if _base.nil?
638
+ target = superclass
639
+ loop do
640
+ break unless target.respond_to?(:base)
641
+ _base = target.base
642
+ break if _base
643
+ target = target.superclass
644
+ end
645
+ end
646
+ _prefix = prefix
647
+
648
+ _base ||= connection.naming_contexts.first
649
+ return _prefix if _base.blank?
650
+
651
+ _base = DN.parse(_base)
652
+ _base = _prefix + _base if _prefix
653
+ _base
654
+ end
655
+ end
656
+
657
+ self.scope = :sub
658
+ self.required_classes = ['top']
659
+ self.recommended_classes = []
660
+ self.excluded_classes = []
661
+
662
+ include Enumerable
663
+
664
+ ### All instance methods, etc
665
+
666
+ # new
667
+ #
668
+ # Creates a new instance of Base initializing all class and all
669
+ # initialization. Defines local defaults. See examples If multiple values
670
+ # exist for dn_attribute, the first one put here will be authoritative
671
+ def initialize(attributes=nil)
672
+ init_base
673
+ @new_entry = true
674
+ initial_classes = required_classes | recommended_classes
675
+ case attributes
676
+ when nil
677
+ self.classes = initial_classes
678
+ when String, Array, DN
679
+ self.classes = initial_classes
680
+ self.dn = attributes
681
+ when Hash
682
+ classes, attributes = extract_object_class(attributes)
683
+ self.classes = classes | initial_classes
684
+ normalized_attributes = {}
685
+ attributes.each do |key, value|
686
+ real_key = to_real_attribute_name(key) || key
687
+ normalized_attributes[real_key] = value
688
+ end
689
+ self.dn = normalized_attributes.delete(dn_attribute)
690
+ self.attributes = normalized_attributes
691
+ else
692
+ format = _("'%s' must be either nil, DN value as ActiveLdap::DN, " \
693
+ "String or Array or attributes as Hash")
694
+ raise ArgumentError, format % attributes.inspect
695
+ end
696
+ yield self if block_given?
697
+ end
698
+
699
+ # Returns true if the +comparison_object+ is the same object, or is of
700
+ # the same type and has the same dn.
701
+ def ==(comparison_object)
702
+ comparison_object.equal?(self) or
703
+ (comparison_object.instance_of?(self.class) and
704
+ comparison_object.dn == dn and
705
+ !comparison_object.new_entry?)
706
+ end
707
+
708
+ # Delegates to ==
709
+ def eql?(comparison_object)
710
+ self == (comparison_object)
711
+ end
712
+
713
+ # Delegates to id in order to allow two records of the same type and id
714
+ # to work with something like:
715
+ # [ User.find("a"), User.find("b"), User.find("c") ] &
716
+ # [ User.find("a"), User.find("d") ] # => [ User.find("a") ]
717
+ def hash
718
+ return super if @_hashing # workaround for GetText :<
719
+ _dn = nil
720
+ begin
721
+ @_hashing = true
722
+ _dn = dn
723
+ rescue DistinguishedNameInvalid, DistinguishedNameNotSetError
724
+ return super
725
+ ensure
726
+ @_hashing = false
727
+ end
728
+ _dn.hash
729
+ end
730
+
731
+ def may
732
+ entry_attribute.may
733
+ end
734
+
735
+ def must
736
+ entry_attribute.must
737
+ end
738
+
739
+ # attributes
740
+ #
741
+ # Return attribute methods so that a program can determine available
742
+ # attributes dynamically without schema awareness
743
+ def attribute_names(normalize=false)
744
+ entry_attribute.names(normalize)
745
+ end
746
+
747
+ def attribute_present?(name)
748
+ values = get_attribute(name, true)
749
+ !values.empty? or values.any? {|x| !(x and x.empty?)}
750
+ end
751
+
752
+ # exist?
753
+ #
754
+ # Return whether the entry exists in LDAP or not
755
+ def exist?
756
+ self.class.exists?(dn)
757
+ end
758
+ alias_method(:exists?, :exist?)
759
+
760
+ # dn
761
+ #
762
+ # Return the authoritative dn
763
+ def dn
764
+ @dn ||= compute_dn
765
+ end
766
+
767
+ def id
768
+ get_attribute(dn_attribute_with_fallback)
769
+ end
770
+
771
+ def to_param
772
+ id
773
+ end
774
+
775
+ # Returns this entity’s dn wrapped in an Array or nil if the entity' s dn is not set.
776
+ def to_key
777
+ [dn]
778
+ rescue DistinguishedNameNotSetError
779
+ nil
780
+ end
781
+
782
+ def dn=(value)
783
+ set_attribute(dn_attribute_with_fallback, value)
784
+ end
785
+ alias_method(:id=, :dn=)
786
+
787
+ alias_method(:dn_attribute_of_class, :dn_attribute)
788
+ def dn_attribute
789
+ ensure_update_dn
790
+ _dn_attribute = @dn_attribute || dn_attribute_of_class
791
+ to_real_attribute_name(_dn_attribute) || _dn_attribute
792
+ end
793
+
794
+ def default_search_attribute
795
+ self.class.default_search_attribute
796
+ end
797
+
798
+ # Updates a given attribute and saves immediately
799
+ def update_attribute(name, value)
800
+ send("#{name}=", value)
801
+ save
802
+ end
803
+
804
+ # This performs a bulk update of attributes and immediately
805
+ # calls #save.
806
+ def update_attributes(attrs)
807
+ self.attributes = attrs
808
+ save
809
+ end
810
+
811
+ def update_attributes!(attrs)
812
+ self.attributes = attrs
813
+ save!
814
+ end
815
+
816
+ # This returns the key value pairs in @data with all values
817
+ # cloned
818
+ def attributes
819
+ @simplified_data ||= simplify_data(@data)
820
+ @simplified_data.clone
821
+ end
822
+
823
+ # This allows a bulk update to the attributes of a record
824
+ # without forcing an immediate save or validation.
825
+ #
826
+ # It is unwise to attempt objectClass updates this way.
827
+ # Also be sure to only pass in key-value pairs of your choosing.
828
+ # Do not let URL/form hackers supply the keys.
829
+ def attributes=(new_attributes)
830
+ return if new_attributes.blank?
831
+ assign_attributes(new_attributes)
832
+ end
833
+
834
+ def assign_attributes(new_attributes, options={})
835
+ return if new_attributes.blank?
836
+
837
+ _schema = _local_entry_attribute = nil
838
+ if options[:without_protection]
839
+ targets = new_attributes
840
+ else
841
+ targets = sanitize_for_mass_assignment(new_attributes, options[:role])
842
+ end
843
+ targets.each do |key, value|
844
+ setter = "#{key}="
845
+ unless respond_to?(setter)
846
+ _schema ||= schema
847
+ attribute = _schema.attribute(key)
848
+ next if attribute.id.nil?
849
+ _local_entry_attribute ||= local_entry_attribute
850
+ _local_entry_attribute.register(attribute)
851
+ end
852
+ send(setter, value)
853
+ end
854
+ end
855
+
856
+ def to_ldif_record
857
+ super(dn, normalize_data(@data))
858
+ end
859
+
860
+ def to_ldif
861
+ Ldif.new([to_ldif_record]).to_s
862
+ end
863
+
864
+ def to_xml(options={})
865
+ options = options.dup
866
+ options[:root] ||= (self.class.name || '').underscore
867
+ options[:root] = 'anonymous' if options[:root].blank?
868
+ [:only, :except].each do |attribute_names_key|
869
+ names = options[attribute_names_key]
870
+ next if names.nil?
871
+ options[attribute_names_key] = names.collect do |name|
872
+ if name.to_s.downcase == "dn"
873
+ "dn"
874
+ else
875
+ to_real_attribute_name(name)
876
+ end
877
+ end.compact
878
+ end
879
+ XML.new(dn, normalize_data(@data), schema).to_s(options)
880
+ end
881
+
882
+ def to_s
883
+ to_ldif
884
+ end
885
+
886
+ def have_attribute?(name, except=[])
887
+ real_name = to_real_attribute_name(name)
888
+ !real_name.nil? and !except.include?(real_name)
889
+ end
890
+ alias_method :has_attribute?, :have_attribute?
891
+
892
+ def [](name, force_array=false)
893
+ if name == "dn"
894
+ array_of(dn, force_array)
895
+ else
896
+ get_attribute(name, force_array)
897
+ end
898
+ end
899
+
900
+ def []=(name, value)
901
+ set_attribute(name, value)
902
+ end
903
+
904
+ def bind(config_or_password={}, config_or_ignore=nil, &block)
905
+ if config_or_password.is_a?(String)
906
+ config = (config_or_ignore || {}).merge(:password => config_or_password)
907
+ else
908
+ config = config_or_password
909
+ end
910
+ config = {:bind_dn => dn, :allow_anonymous => false}.merge(config)
911
+ config[:password_block] ||= block if block_given?
912
+ setup_connection(config)
913
+
914
+ before_connection = @connection
915
+ begin
916
+ @connection = nil
917
+ connection.connect
918
+ @connection = connection
919
+ clear_connection_based_cache
920
+ clear_association_cache
921
+ rescue ActiveLdap::Error
922
+ remove_connection
923
+ @connection = before_connection
924
+ raise
925
+ end
926
+ true
927
+ end
928
+
929
+ def clear_connection_based_cache
930
+ @schema = nil
931
+ @local_entry_attribute = nil
932
+ clear_object_class_based_cache
933
+ end
934
+
935
+ def clear_object_class_based_cache
936
+ @entry_attribute = nil
937
+ @real_names = {}
938
+ @changed_attributes.reject! do |key, _|
939
+ not attribute_method?(key)
940
+ end
941
+ end
942
+
943
+ def schema
944
+ @schema ||= super
945
+ end
946
+
947
+ def base
948
+ @base ||= compute_base
949
+ end
950
+
951
+ def base=(object_local_base)
952
+ ensure_update_dn
953
+ @dn = nil
954
+ @base = nil
955
+ @base_value = object_local_base
956
+ end
957
+
958
+ alias_method :scope_of_class, :scope
959
+ def scope
960
+ @scope || scope_of_class
961
+ end
962
+
963
+ def scope=(scope)
964
+ self.class.validate_scope(scope)
965
+ @scope = scope
966
+ end
967
+
968
+ def delete_all(options={})
969
+ super({:base => dn}.merge(options || {}))
970
+ end
971
+
972
+ def destroy_all(options={})
973
+ super({:base => dn}.merge(options || {}))
974
+ end
975
+
976
+ def inspect
977
+ object_classes = entry_attribute.object_classes
978
+ inspected_object_classes = object_classes.collect do |object_class|
979
+ object_class.name
980
+ end.join(', ')
981
+ must_attributes = must.collect(&:name).sort.join(', ')
982
+ may_attributes = may.collect(&:name).sort.join(', ')
983
+ inspected_attributes = attribute_names.sort.collect do |name|
984
+ inspect_attribute(name)
985
+ end.join(', ')
986
+ result = "\#<#{self.class} objectClass:<#{inspected_object_classes}>, "
987
+ result << "must:<#{must_attributes}>, may:<#{may_attributes}>, "
988
+ result << "#{inspected_attributes}>"
989
+ result
990
+ end
991
+
992
+ private
993
+ def dn_attribute_with_fallback
994
+ begin
995
+ dn_attribute
996
+ rescue DistinguishedNameInvalid
997
+ _dn_attribute = @dn_attribute || dn_attribute_of_class
998
+ _dn_attribute = to_real_attribute_name(_dn_attribute) || _dn_attribute
999
+ raise if _dn_attribute.nil?
1000
+ _dn_attribute
1001
+ end
1002
+ end
1003
+
1004
+ def inspect_attribute(name)
1005
+ values = get_attribute(name, true)
1006
+ values.collect do |value|
1007
+ if value.is_a?(String) and value.length > 50
1008
+ "#{value[0, 50]}...".inspect
1009
+ elsif value.is_a?(Date) || value.is_a?(Time)
1010
+ "#{value.to_s(:db)}"
1011
+ else
1012
+ value.inspect
1013
+ end
1014
+ end
1015
+ "#{name}: #{values.inspect}"
1016
+ end
1017
+
1018
+ def find_object_class_values(data)
1019
+ data["objectClass"] || data["objectclass"]
1020
+ end
1021
+
1022
+ def attribute_name_resolvable_without_connection?
1023
+ @entry_attribute and @local_entry_attribute
1024
+ end
1025
+
1026
+ def entry_attribute
1027
+ @entry_attribute ||=
1028
+ connection.entry_attribute(find_object_class_values(@data) || [])
1029
+ end
1030
+
1031
+ def local_entry_attribute
1032
+ @local_entry_attribute ||= connection.entry_attribute([])
1033
+ end
1034
+
1035
+ def extract_object_class(attributes)
1036
+ classes = []
1037
+ attrs = {}
1038
+ attributes.each do |key, value|
1039
+ key = key.to_s
1040
+ if /\Aobject_?class\z/i =~ key
1041
+ classes.concat(value.to_a)
1042
+ else
1043
+ attrs[key] = value
1044
+ end
1045
+ end
1046
+ [classes, attributes]
1047
+ end
1048
+
1049
+ def init_base
1050
+ init_instance_variables
1051
+ end
1052
+
1053
+ def initialize_by_ldap_data(dn, attributes)
1054
+ init_base
1055
+ dn = Compatible.convert_to_utf8_encoded_object(dn)
1056
+ attributes = Compatible.convert_to_utf8_encoded_object(attributes)
1057
+ @original_dn = dn.clone
1058
+ @dn = dn
1059
+ @base = nil
1060
+ @base_value = nil
1061
+ @new_entry = false
1062
+ @dn_is_base = false
1063
+ @ldap_data = attributes
1064
+ classes, attributes = extract_object_class(attributes)
1065
+ self.classes = classes
1066
+ self.dn = dn
1067
+ initialize_attributes(attributes)
1068
+ yield self if block_given?
1069
+ end
1070
+
1071
+ def initialize_attributes(attributes)
1072
+ _schema = _local_entry_attribute = nil
1073
+ targets = sanitize_for_mass_assignment(attributes)
1074
+ targets.each do |key, value|
1075
+ unless have_attribute?(key)
1076
+ _schema ||= schema
1077
+ attribute = _schema.attribute(key)
1078
+ _local_entry_attribute ||= local_entry_attribute
1079
+ _local_entry_attribute.register(attribute)
1080
+ end
1081
+ set_attribute(key, value)
1082
+ end
1083
+ @changed_attributes.clear
1084
+ end
1085
+ private :initialize_attributes
1086
+
1087
+ def instantiate(args)
1088
+ dn, attributes, options = args
1089
+ options ||= {}
1090
+
1091
+ obj = self.class.allocate
1092
+ obj.connection = options[:connection] || @connection
1093
+ obj.instance_eval do
1094
+ initialize_by_ldap_data(dn, attributes)
1095
+ end
1096
+ obj
1097
+ end
1098
+
1099
+ def to_real_attribute_name(name, allow_normalized_name=true)
1100
+ return name if name.nil?
1101
+ if allow_normalized_name
1102
+ entry_attribute.normalize(name, allow_normalized_name) ||
1103
+ local_entry_attribute.normalize(name, allow_normalized_name)
1104
+ else
1105
+ @real_names[name] ||=
1106
+ entry_attribute.normalize(name, false) ||
1107
+ local_entry_attribute.normalize(name, false)
1108
+ end
1109
+ end
1110
+
1111
+ # enforce_type
1112
+ #
1113
+ # enforce_type applies your changes without attempting to write to LDAP.
1114
+ # This means that if you set userCertificate to somebinary value, it will
1115
+ # wrap it up correctly.
1116
+ def enforce_type(key, value)
1117
+ # Enforce attribute value formatting
1118
+ normalize_attribute(key, value)[1]
1119
+ end
1120
+
1121
+ def init_instance_variables
1122
+ @mutex = Mutex.new
1123
+ @data = {} # where the r/w entry data is stored
1124
+ @ldap_data = {} # original ldap entry data
1125
+ @dn_attribute = nil
1126
+ @base = nil
1127
+ @scope = nil
1128
+ @dn = nil
1129
+ @dn_is_base = false
1130
+ @dn_split_value = nil
1131
+ @connection ||= nil
1132
+ @_hashing = false
1133
+ @previously_changed = []
1134
+ @changed_attributes = {}
1135
+ clear_connection_based_cache
1136
+ end
1137
+
1138
+ def register_new_dn_attribute(name, value)
1139
+ @dn = nil
1140
+ @dn_is_base = false
1141
+ if value.blank?
1142
+ @dn_split_value = nil
1143
+ [name, nil]
1144
+ else
1145
+ new_name, new_value, raw_new_value, new_bases = split_dn_value(value)
1146
+ @dn_split_value = [new_name, new_value, new_bases]
1147
+ if new_name.nil? and new_value.nil?
1148
+ new_name, raw_new_value = new_bases[0].to_a[0]
1149
+ end
1150
+ [to_real_attribute_name(new_name) || name,
1151
+ raw_new_value || value]
1152
+ end
1153
+ end
1154
+
1155
+ def update_dn(new_name, new_value, bases)
1156
+ if new_name.nil? and new_value.nil?
1157
+ @dn_is_base = true
1158
+ @base = nil
1159
+ @base_value = nil
1160
+ attr, value = bases[0].to_a[0]
1161
+ @dn_attribute = attr
1162
+ _ = value # for suppress a warning on Ruby 1.9.3
1163
+ else
1164
+ new_name ||= @dn_attribute || dn_attribute_of_class
1165
+ new_name = to_real_attribute_name(new_name)
1166
+ if new_name.nil?
1167
+ new_name = @dn_attribute || dn_attribute_of_class
1168
+ new_name = to_real_attribute_name(new_name)
1169
+ end
1170
+ new_bases = bases.empty? ? nil : DN.new(*bases).to_s
1171
+ dn_components = ["#{new_name}=#{new_value}",
1172
+ new_bases,
1173
+ self.class.base.to_s]
1174
+ dn_components = dn_components.find_all {|component| !component.blank?}
1175
+ DN.parse(dn_components.join(','))
1176
+ @base = nil
1177
+ @base_value = new_bases
1178
+ @dn_attribute = new_name
1179
+ end
1180
+ end
1181
+
1182
+ def split_dn_value(value)
1183
+ dn_value = relative_dn_value = nil
1184
+ begin
1185
+ dn_value = value if value.is_a?(DN)
1186
+ dn_value ||= DN.parse(value)
1187
+ rescue DistinguishedNameInvalid
1188
+ begin
1189
+ dn_value = DN.parse("#{dn_attribute}=#{value}")
1190
+ rescue DistinguishedNameInvalid
1191
+ return [nil, value, value, []]
1192
+ end
1193
+ end
1194
+
1195
+ val = bases = nil
1196
+ begin
1197
+ relative_dn_value = dn_value
1198
+ base_of_class = self.class.base
1199
+ relative_dn_value -= base_of_class if base_of_class
1200
+ if relative_dn_value.rdns.empty?
1201
+ val = []
1202
+ bases = dn_value.rdns
1203
+ else
1204
+ val, *bases = relative_dn_value.rdns
1205
+ end
1206
+ rescue ArgumentError
1207
+ val, *bases = dn_value.rdns
1208
+ end
1209
+
1210
+ dn_attribute_name, dn_attribute_value = val.to_a[0]
1211
+ escaped_dn_attribute_value = nil
1212
+ unless dn_attribute_value.nil?
1213
+ escaped_dn_attribute_value = DN.escape_value(dn_attribute_value)
1214
+ end
1215
+ [dn_attribute_name, escaped_dn_attribute_value,
1216
+ dn_attribute_value, bases]
1217
+ end
1218
+
1219
+ def need_update_dn?
1220
+ not @dn_split_value.nil?
1221
+ end
1222
+
1223
+ def ensure_update_dn
1224
+ return unless need_update_dn?
1225
+ @mutex.synchronize do
1226
+ if @dn_split_value
1227
+ update_dn(*@dn_split_value)
1228
+ @dn_split_value = nil
1229
+ end
1230
+ end
1231
+ end
1232
+
1233
+ def compute_dn
1234
+ return base if @dn_is_base
1235
+
1236
+ ensure_update_dn
1237
+ dn_value = id
1238
+ if dn_value.nil?
1239
+ format = _("%s's DN attribute (%s) isn't set")
1240
+ message = format % [self.inspect, dn_attribute]
1241
+ raise DistinguishedNameNotSetError.new, message
1242
+ end
1243
+ dn_value = DN.escape_value(dn_value.to_s)
1244
+ _base = base
1245
+ _base = nil if _base.blank?
1246
+ DN.parse(["#{dn_attribute}=#{dn_value}", _base].compact.join(","))
1247
+ end
1248
+
1249
+ def compute_base
1250
+ base_of_class = self.class.base
1251
+ if @base_value.nil?
1252
+ base_of_class
1253
+ else
1254
+ base_of_object = DN.parse(@base_value)
1255
+ base_of_object += base_of_class if base_of_class
1256
+ base_of_object
1257
+ end
1258
+ end
1259
+
1260
+ # array_of
1261
+ #
1262
+ # Returns the array form of a value, or not an array if
1263
+ # false is passed in.
1264
+ def array_of(value, to_a=true)
1265
+ case value
1266
+ when Array
1267
+ if to_a or value.size > 1
1268
+ value.collect {|v| array_of(v, false)}.compact
1269
+ else
1270
+ if value.empty?
1271
+ nil
1272
+ else
1273
+ array_of(value.first, to_a)
1274
+ end
1275
+ end
1276
+ when Hash
1277
+ if to_a
1278
+ [value]
1279
+ else
1280
+ result = {}
1281
+ value.each {|k, v| result[k] = array_of(v, to_a)}
1282
+ result
1283
+ end
1284
+ else
1285
+ to_a ? [value] : value
1286
+ end
1287
+ end
1288
+
1289
+ def normalize_data(data, except=[])
1290
+ _schema = schema
1291
+ result = {}
1292
+ data.each do |key, values|
1293
+ next if except.include?(key)
1294
+ real_name = to_real_attribute_name(key)
1295
+ next if real_name and except.include?(real_name)
1296
+ real_name ||= key
1297
+ next if _schema.attribute(real_name).id.nil?
1298
+ result[real_name] ||= []
1299
+ result[real_name].concat(enforce_type(real_name, values))
1300
+ end
1301
+ result
1302
+ end
1303
+
1304
+ def simplify_data(data)
1305
+ _schema = schema
1306
+ result = {}
1307
+ data.each do |key, values|
1308
+ attribute = _schema.attribute(key)
1309
+ if attribute.single_value? and values.is_a?(Array) and values.size == 1
1310
+ values = values[0]
1311
+ end
1312
+ result[key] = type_cast(attribute, values)
1313
+ end
1314
+ result
1315
+ end
1316
+
1317
+ def collect_modified_attributes(ldap_data, data)
1318
+ klass = self.class
1319
+ _dn_attribute = dn_attribute
1320
+ new_dn_value = nil
1321
+ attributes = []
1322
+
1323
+ # Now that all the options will be treated as unique attributes
1324
+ # we can see what's changed and add anything that is brand-spankin'
1325
+ # new.
1326
+ ldap_data.each do |k, v|
1327
+ value = data[k] || []
1328
+
1329
+ next if v == value
1330
+
1331
+ value = klass.remove_blank_value(value) || []
1332
+ next if v == value
1333
+
1334
+ if klass.blank_value?(value) and
1335
+ schema.attribute(k).binary_required?
1336
+ value = [{'binary' => []}]
1337
+ end
1338
+ if k == _dn_attribute
1339
+ new_dn_value = value[0]
1340
+ else
1341
+ attributes.push([:replace, k, value])
1342
+ end
1343
+ end
1344
+
1345
+ data.each do |k, v|
1346
+ value = v || []
1347
+ next if ldap_data.has_key?(k)
1348
+
1349
+ value = klass.remove_blank_value(value) || []
1350
+ next if klass.blank_value?(value)
1351
+
1352
+ # Detect subtypes and account for them
1353
+ # REPLACE will function like ADD, but doesn't hit EQUALITY problems
1354
+ # TODO: Added equality(attr) to Schema
1355
+ attributes.push([:replace, k, value])
1356
+ end
1357
+
1358
+ [new_dn_value, attributes]
1359
+ end
1360
+
1361
+ def collect_all_attributes(data)
1362
+ dn_attr = dn_attribute
1363
+ dn_value = data[dn_attr]
1364
+
1365
+ attributes = []
1366
+ attributes.push([dn_attr, dn_value])
1367
+
1368
+ oc_value = data['objectClass']
1369
+ attributes.push(['objectClass', oc_value])
1370
+ except_keys = ['objectClass', dn_attr].collect(&:downcase)
1371
+ data.each do |key, value|
1372
+ next if except_keys.include?(key.downcase)
1373
+ value = self.class.remove_blank_value(value)
1374
+ next if self.class.blank_value?(value)
1375
+
1376
+ attributes.push([key, value])
1377
+ end
1378
+
1379
+ attributes
1380
+ end
1381
+
1382
+ def prepare_data_for_saving
1383
+ # Expand subtypes to real ldap_data attributes
1384
+ # We can't reuse @ldap_data because an exception would leave
1385
+ # an object in an unknown state
1386
+ ldap_data = normalize_data(@ldap_data)
1387
+
1388
+ # Expand subtypes to real data attributes, but leave @data alone
1389
+ object_classes = find_object_class_values(@ldap_data) || []
1390
+ original_attributes =
1391
+ connection.entry_attribute(object_classes).names
1392
+ bad_attrs = original_attributes - entry_attribute.names
1393
+ data = normalize_data(@data, bad_attrs)
1394
+
1395
+ success = yield(data, ldap_data)
1396
+
1397
+ if success
1398
+ @ldap_data = data.clone
1399
+ # Delete items disallowed by objectclasses.
1400
+ # They should have been removed from ldap.
1401
+ bad_attrs.each do |remove_me|
1402
+ @ldap_data.delete(remove_me)
1403
+ end
1404
+ @original_dn = dn.clone
1405
+ end
1406
+
1407
+ success
1408
+ end
1409
+ end # Base
1410
+ end # ActiveLdap