powerhome-activeldap 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +6 -0
  3. data/COPYING +340 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +59 -0
  6. data/README.textile +140 -0
  7. data/TODO +32 -0
  8. data/benchmark/README.md +64 -0
  9. data/benchmark/bench-backend.rb +247 -0
  10. data/benchmark/bench-instantiate.rb +98 -0
  11. data/benchmark/config.yaml.sample +5 -0
  12. data/doc/text/development.textile +54 -0
  13. data/doc/text/news.textile +811 -0
  14. data/doc/text/rails.textile +144 -0
  15. data/doc/text/tutorial.textile +1010 -0
  16. data/examples/config.yaml.example +5 -0
  17. data/examples/example.der +0 -0
  18. data/examples/example.jpg +0 -0
  19. data/examples/groupadd +41 -0
  20. data/examples/groupdel +35 -0
  21. data/examples/groupls +49 -0
  22. data/examples/groupmod +42 -0
  23. data/examples/lpasswd +55 -0
  24. data/examples/objects/group.rb +13 -0
  25. data/examples/objects/ou.rb +4 -0
  26. data/examples/objects/user.rb +20 -0
  27. data/examples/ouadd +38 -0
  28. data/examples/useradd +45 -0
  29. data/examples/useradd-binary +53 -0
  30. data/examples/userdel +34 -0
  31. data/examples/userls +50 -0
  32. data/examples/usermod +42 -0
  33. data/examples/usermod-binary-add +50 -0
  34. data/examples/usermod-binary-add-time +54 -0
  35. data/examples/usermod-binary-del +48 -0
  36. data/examples/usermod-lang-add +43 -0
  37. data/lib/active_ldap.rb +85 -0
  38. data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
  39. data/lib/active_ldap/acts/tree.rb +78 -0
  40. data/lib/active_ldap/adapter/base.rb +707 -0
  41. data/lib/active_ldap/adapter/jndi.rb +184 -0
  42. data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
  43. data/lib/active_ldap/adapter/ldap.rb +290 -0
  44. data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
  45. data/lib/active_ldap/adapter/net_ldap.rb +309 -0
  46. data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
  47. data/lib/active_ldap/association/belongs_to.rb +47 -0
  48. data/lib/active_ldap/association/belongs_to_many.rb +58 -0
  49. data/lib/active_ldap/association/children.rb +21 -0
  50. data/lib/active_ldap/association/collection.rb +105 -0
  51. data/lib/active_ldap/association/has_many.rb +31 -0
  52. data/lib/active_ldap/association/has_many_utils.rb +44 -0
  53. data/lib/active_ldap/association/has_many_wrap.rb +75 -0
  54. data/lib/active_ldap/association/proxy.rb +107 -0
  55. data/lib/active_ldap/associations.rb +205 -0
  56. data/lib/active_ldap/attribute_methods.rb +23 -0
  57. data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
  58. data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
  59. data/lib/active_ldap/attribute_methods/query.rb +31 -0
  60. data/lib/active_ldap/attribute_methods/read.rb +44 -0
  61. data/lib/active_ldap/attribute_methods/write.rb +38 -0
  62. data/lib/active_ldap/attributes.rb +176 -0
  63. data/lib/active_ldap/base.rb +1410 -0
  64. data/lib/active_ldap/callbacks.rb +71 -0
  65. data/lib/active_ldap/command.rb +49 -0
  66. data/lib/active_ldap/compatible.rb +44 -0
  67. data/lib/active_ldap/configuration.rb +147 -0
  68. data/lib/active_ldap/connection.rb +299 -0
  69. data/lib/active_ldap/distinguished_name.rb +291 -0
  70. data/lib/active_ldap/entry_attribute.rb +78 -0
  71. data/lib/active_ldap/escape.rb +12 -0
  72. data/lib/active_ldap/get_text.rb +20 -0
  73. data/lib/active_ldap/get_text/parser.rb +161 -0
  74. data/lib/active_ldap/helper.rb +92 -0
  75. data/lib/active_ldap/human_readable.rb +133 -0
  76. data/lib/active_ldap/ldap_error.rb +74 -0
  77. data/lib/active_ldap/ldif.rb +930 -0
  78. data/lib/active_ldap/log_subscriber.rb +50 -0
  79. data/lib/active_ldap/object_class.rb +95 -0
  80. data/lib/active_ldap/operations.rb +624 -0
  81. data/lib/active_ldap/persistence.rb +100 -0
  82. data/lib/active_ldap/populate.rb +53 -0
  83. data/lib/active_ldap/railtie.rb +43 -0
  84. data/lib/active_ldap/railties/controller_runtime.rb +48 -0
  85. data/lib/active_ldap/schema.rb +701 -0
  86. data/lib/active_ldap/schema/syntaxes.rb +422 -0
  87. data/lib/active_ldap/timeout.rb +75 -0
  88. data/lib/active_ldap/timeout_stub.rb +17 -0
  89. data/lib/active_ldap/user_password.rb +99 -0
  90. data/lib/active_ldap/validations.rb +200 -0
  91. data/lib/active_ldap/version.rb +3 -0
  92. data/lib/active_ldap/xml.rb +139 -0
  93. data/lib/rails/generators/active_ldap/model/USAGE +18 -0
  94. data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
  95. data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
  96. data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
  97. data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
  98. data/po/en/active-ldap.po +4029 -0
  99. data/po/ja/active-ldap.po +4060 -0
  100. data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
  101. data/test/al-test-utils.rb +428 -0
  102. data/test/command.rb +111 -0
  103. data/test/config.yaml.sample +6 -0
  104. data/test/fixtures/lower_case_object_class_schema.rb +802 -0
  105. data/test/run-test.rb +34 -0
  106. data/test/test_acts_as_tree.rb +60 -0
  107. data/test/test_adapter.rb +121 -0
  108. data/test/test_associations.rb +701 -0
  109. data/test/test_attributes.rb +117 -0
  110. data/test/test_base.rb +1214 -0
  111. data/test/test_base_per_instance.rb +61 -0
  112. data/test/test_bind.rb +62 -0
  113. data/test/test_callback.rb +31 -0
  114. data/test/test_configuration.rb +40 -0
  115. data/test/test_connection.rb +82 -0
  116. data/test/test_connection_per_class.rb +112 -0
  117. data/test/test_connection_per_dn.rb +112 -0
  118. data/test/test_dirty.rb +98 -0
  119. data/test/test_dn.rb +172 -0
  120. data/test/test_find.rb +176 -0
  121. data/test/test_groupadd.rb +50 -0
  122. data/test/test_groupdel.rb +46 -0
  123. data/test/test_groupls.rb +107 -0
  124. data/test/test_groupmod.rb +51 -0
  125. data/test/test_ldif.rb +1890 -0
  126. data/test/test_load.rb +133 -0
  127. data/test/test_lpasswd.rb +75 -0
  128. data/test/test_object_class.rb +74 -0
  129. data/test/test_persistence.rb +131 -0
  130. data/test/test_reflection.rb +175 -0
  131. data/test/test_schema.rb +559 -0
  132. data/test/test_syntax.rb +444 -0
  133. data/test/test_user.rb +217 -0
  134. data/test/test_user_password.rb +108 -0
  135. data/test/test_useradd-binary.rb +62 -0
  136. data/test/test_useradd.rb +57 -0
  137. data/test/test_userdel.rb +48 -0
  138. data/test/test_userls.rb +91 -0
  139. data/test/test_usermod-binary-add-time.rb +65 -0
  140. data/test/test_usermod-binary-add.rb +64 -0
  141. data/test/test_usermod-binary-del.rb +66 -0
  142. data/test/test_usermod-lang-add.rb +59 -0
  143. data/test/test_usermod.rb +58 -0
  144. data/test/test_validation.rb +274 -0
  145. metadata +379 -0
@@ -0,0 +1,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