ruby-activeldap 0.8.2 → 0.8.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 (83) hide show
  1. data/test/test_adapter.rb +17 -0
  2. data/test/test_associations.rb +19 -0
  3. data/test/test_attributes.rb +2 -1
  4. data/test/test_base.rb +28 -1
  5. data/test/test_base_per_instance.rb +2 -1
  6. data/test/test_callback.rb +2 -2
  7. data/test/test_connection.rb +2 -1
  8. data/test/test_connection_per_dn.rb +81 -0
  9. data/test/test_dn.rb +3 -2
  10. data/test/test_find.rb +35 -1
  11. data/test/test_object_class.rb +12 -1
  12. data/test/test_reflection.rb +16 -10
  13. data/test/test_schema.rb +141 -2
  14. data/test/test_user.rb +14 -4
  15. metadata +7 -104
  16. data/CHANGES +0 -397
  17. data/COPYING +0 -340
  18. data/LICENSE +0 -58
  19. data/Manifest.txt +0 -99
  20. data/README +0 -85
  21. data/Rakefile +0 -70
  22. data/TODO +0 -23
  23. data/benchmark/bench-al.rb +0 -152
  24. data/examples/config.yaml.example +0 -5
  25. data/examples/example.der +0 -0
  26. data/examples/example.jpg +0 -0
  27. data/examples/groupadd +0 -41
  28. data/examples/groupdel +0 -35
  29. data/examples/groupls +0 -49
  30. data/examples/groupmod +0 -42
  31. data/examples/lpasswd +0 -55
  32. data/examples/objects/group.rb +0 -13
  33. data/examples/objects/ou.rb +0 -4
  34. data/examples/objects/user.rb +0 -20
  35. data/examples/ouadd +0 -38
  36. data/examples/useradd +0 -45
  37. data/examples/useradd-binary +0 -50
  38. data/examples/userdel +0 -34
  39. data/examples/userls +0 -50
  40. data/examples/usermod +0 -42
  41. data/examples/usermod-binary-add +0 -47
  42. data/examples/usermod-binary-add-time +0 -51
  43. data/examples/usermod-binary-del +0 -48
  44. data/examples/usermod-lang-add +0 -43
  45. data/lib/active_ldap.rb +0 -964
  46. data/lib/active_ldap/adapter/base.rb +0 -461
  47. data/lib/active_ldap/adapter/ldap.rb +0 -232
  48. data/lib/active_ldap/adapter/ldap_ext.rb +0 -69
  49. data/lib/active_ldap/adapter/net_ldap.rb +0 -288
  50. data/lib/active_ldap/adapter/net_ldap_ext.rb +0 -29
  51. data/lib/active_ldap/association/belongs_to.rb +0 -40
  52. data/lib/active_ldap/association/belongs_to_many.rb +0 -39
  53. data/lib/active_ldap/association/collection.rb +0 -80
  54. data/lib/active_ldap/association/has_many.rb +0 -40
  55. data/lib/active_ldap/association/has_many_wrap.rb +0 -55
  56. data/lib/active_ldap/association/proxy.rb +0 -89
  57. data/lib/active_ldap/associations.rb +0 -162
  58. data/lib/active_ldap/attributes.rb +0 -203
  59. data/lib/active_ldap/base.rb +0 -1510
  60. data/lib/active_ldap/callbacks.rb +0 -19
  61. data/lib/active_ldap/command.rb +0 -46
  62. data/lib/active_ldap/configuration.rb +0 -106
  63. data/lib/active_ldap/connection.rb +0 -142
  64. data/lib/active_ldap/distinguished_name.rb +0 -246
  65. data/lib/active_ldap/ldap_error.rb +0 -74
  66. data/lib/active_ldap/object_class.rb +0 -74
  67. data/lib/active_ldap/schema.rb +0 -299
  68. data/lib/active_ldap/timeout.rb +0 -75
  69. data/lib/active_ldap/timeout_stub.rb +0 -17
  70. data/lib/active_ldap/user_password.rb +0 -92
  71. data/lib/active_ldap/validations.rb +0 -76
  72. data/rails/plugin/active_ldap/README +0 -54
  73. data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +0 -7
  74. data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +0 -21
  75. data/rails/plugin/active_ldap/init.rb +0 -12
  76. data/test/TODO +0 -2
  77. data/test/al-test-utils.rb +0 -381
  78. data/test/command.rb +0 -62
  79. data/test/config.yaml.sample +0 -6
  80. data/test/run-test.rb +0 -29
  81. data/test/test-unit-ext.rb +0 -2
  82. data/test/test-unit-ext/always-show-result.rb +0 -28
  83. data/test/test-unit-ext/priority.rb +0 -163
@@ -1,203 +0,0 @@
1
- module ActiveLdap
2
- module Attributes
3
- def self.included(base)
4
- base.extend(ClassMethods)
5
- end
6
-
7
- module ClassMethods
8
- def attr_protected(*attributes)
9
- targets = attributes.collect {|attr| attr.to_s} - protected_attributes
10
- instance_variable_set("@attr_protected", targets)
11
- end
12
-
13
- def protected_attributes
14
- ancestors[0..(ancestors.index(Base))].inject([]) do |result, ancestor|
15
- result + ancestor.instance_eval {@attr_protected ||= []}
16
- end
17
- end
18
-
19
- def normalize_attribute_name(name)
20
- name.to_s.downcase
21
- end
22
-
23
- # Enforce typing:
24
- # Hashes are for subtypes
25
- # Arrays are for multiple entries
26
- def normalize_attribute(name, value)
27
- logger.debug {"stub: called normalize_attribute" +
28
- "(#{name.inspect}, #{value.inspect})"}
29
- if name.nil?
30
- raise RuntimeError, 'The first argument, name, must not be nil. ' +
31
- 'Please report this as a bug!'
32
- end
33
-
34
- name = normalize_attribute_name(name)
35
- rubyish_class_name = Inflector.underscore(value.class.name)
36
- handler = "normalize_attribute_value_of_#{rubyish_class_name}"
37
- if respond_to?(handler, true)
38
- [name, send(handler, name, value)]
39
- else
40
- [name, [value.to_s]]
41
- end
42
- end
43
-
44
- def unnormalize_attributes(attributes)
45
- result = {}
46
- attributes.each do |name, values|
47
- unnormalize_attribute(name, values, result)
48
- end
49
- result
50
- end
51
-
52
- def unnormalize_attribute(name, values, result={})
53
- if values.empty?
54
- result[name] = []
55
- else
56
- values.each do |value|
57
- if value.is_a?(Hash)
58
- suffix, real_value = extract_subtypes(value)
59
- new_name = name + suffix
60
- result[new_name] ||= []
61
- result[new_name].concat(real_value)
62
- else
63
- result[name] ||= []
64
- result[name] << value.dup
65
- end
66
- end
67
- end
68
- result
69
- end
70
-
71
- private
72
- def normalize_attribute_value_of_array(name, value)
73
- if value.size > 1 and schema.single_value?(name)
74
- raise TypeError, "Attribute #{name} can only have a single value"
75
- end
76
- if value.empty?
77
- schema.binary_required?(name) ? [{'binary' => value}] : value
78
- else
79
- value.collect do |entry|
80
- normalize_attribute(name, entry)[1][0]
81
- end
82
- end
83
- end
84
-
85
- def normalize_attribute_value_of_hash(name, value)
86
- if value.keys.size > 1
87
- raise TypeError, "Hashes must have one key-value pair only."
88
- end
89
- unless value.keys[0].match(/^(lang-[a-z][a-z]*)|(binary)$/)
90
- logger.warn {"unknown subtype did not match lang-* or binary:" +
91
- "#{value.keys[0]}"}
92
- end
93
- # Contents MUST be a String or an Array
94
- if !value.has_key?('binary') and schema.binary_required?(name)
95
- suffix, real_value = extract_subtypes(value)
96
- name, values = make_subtypes(name + suffix + ';binary', real_value)
97
- values
98
- else
99
- [value]
100
- end
101
- end
102
-
103
- def normalize_attribute_value_of_nil_class(name, value)
104
- schema.binary_required?(name) ? [{'binary' => []}] : []
105
- end
106
-
107
- def normalize_attribute_value_of_string(name, value)
108
- [schema.binary_required?(name) ? {'binary' => [value]} : value]
109
- end
110
-
111
- def normalize_attribute_value_of_date(name, value)
112
- new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
113
- value.year, value.month, value.mday, 0, 0, 0,
114
- '+0000')
115
- normalize_attribute_value_of_string(name, new_value)
116
- end
117
-
118
- def normalize_attribute_value_of_time(name, value)
119
- new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
120
- 0, 0, 0, value.hour, value.min, value.sec,
121
- value.zone)
122
- normalize_attribute_value_of_string(name, new_value)
123
- end
124
-
125
- def normalize_attribute_value_of_date_time(name, value)
126
- new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
127
- value.year, value.month, value.mday, value.hour,
128
- value.min, value.sec, value.zone)
129
- normalize_attribute_value_of_string(name, new_value)
130
- end
131
-
132
-
133
- # make_subtypes
134
- #
135
- # Makes the Hashized value from the full attributename
136
- # e.g. userCertificate;binary => "some_bin"
137
- # becomes userCertificate => {"binary" => "some_bin"}
138
- def make_subtypes(attr, value)
139
- logger.debug {"stub: called make_subtypes(#{attr.inspect}, " +
140
- "#{value.inspect})"}
141
- return [attr, value] unless attr.match(/;/)
142
-
143
- ret_attr, *subtypes = attr.split(/;/)
144
- return [ret_attr, [make_subtypes_helper(subtypes, value)]]
145
- end
146
-
147
- # make_subtypes_helper
148
- #
149
- # This is a recursive function for building
150
- # nested hashed from multi-subtyped values
151
- def make_subtypes_helper(subtypes, value)
152
- logger.debug {"stub: called make_subtypes_helper" +
153
- "(#{subtypes.inspect}, #{value.inspect})"}
154
- return value if subtypes.size == 0
155
- return {subtypes[0] => make_subtypes_helper(subtypes[1..-1], value)}
156
- end
157
-
158
- # extract_subtypes
159
- #
160
- # Extracts all of the subtypes from a given set of nested hashes
161
- # and returns the attribute suffix and the final true value
162
- def extract_subtypes(value)
163
- logger.debug {"stub: called extract_subtypes(#{value.inspect})"}
164
- subtype = ''
165
- ret_val = value
166
- if value.class == Hash
167
- subtype = ';' + value.keys[0]
168
- ret_val = value[value.keys[0]]
169
- subsubtype = ''
170
- if ret_val.class == Hash
171
- subsubtype, ret_val = extract_subtypes(ret_val)
172
- end
173
- subtype += subsubtype
174
- end
175
- ret_val = [ret_val] unless ret_val.class == Array
176
- return subtype, ret_val
177
- end
178
- end
179
-
180
- private
181
- def remove_attributes_protected_from_mass_assignment(targets)
182
- needless_attributes = {}
183
- (attributes_protected_by_default +
184
- (self.class.protected_attributes || [])).each do |name|
185
- needless_attributes[to_real_attribute_name(name)] = true
186
- end
187
-
188
- targets.collect do |key, value|
189
- [to_real_attribute_name(key), value]
190
- end.reject do |key, value|
191
- key.nil? or needless_attributes[key]
192
- end
193
- end
194
-
195
- def attributes_protected_by_default
196
- [dn_attribute, 'objectClass']
197
- end
198
-
199
- def normalize_attribute_name(name)
200
- self.class.normalize_attribute_name(name)
201
- end
202
- end
203
- end
@@ -1,1510 +0,0 @@
1
- # === activeldap - an OO-interface to LDAP objects inspired by ActiveRecord
2
- # Author: Will Drewry <will@alum.bu.edu>
3
- # License: See LICENSE and COPYING.txt
4
- # Copyright 2004-2006 Will Drewry <will@alum.bu.edu>
5
- # Some portions Copyright 2006 Google Inc
6
- #
7
- # == Summary
8
- # ActiveLdap lets you read and update LDAP entries in a completely object
9
- # oriented fashion, even handling attributes with multiple names seamlessly.
10
- # It was inspired by ActiveRecord so extending it to deal with custom
11
- # LDAP schemas is as effortless as knowing the 'ou' of the objects, and the
12
- # primary key. (fix this up some)
13
- #
14
- # == Example
15
- # irb> require 'active_ldap'
16
- # > true
17
- # irb> user = ActiveLdap::User.new("drewry")
18
- # > #<ActiveLdap::User:0x402e...
19
- # irb> user.cn
20
- # > "foo"
21
- # irb> user.common_name
22
- # > "foo"
23
- # irb> user.cn = "Will Drewry"
24
- # > "Will Drewry"
25
- # irb> user.cn
26
- # > "Will Drewry"
27
- # irb> user.save
28
- #
29
- #
30
-
31
- require 'English'
32
-
33
- module ActiveLdap
34
- # OO-interface to LDAP assuming pam/nss_ldap-style organization with
35
- # Active specifics
36
- # Each subclass does a ldapsearch for the matching entry.
37
- # If no exact match, raise an error.
38
- # If match, change all LDAP attributes in accessor attributes on the object.
39
- # -- these are ACTUALLY populated from schema - see active_ldap/schema.rb
40
- # example
41
- # -- extract objectClasses from match and populate
42
- # Multiple entries become lists.
43
- # If this isn't read-only then lists become multiple entries, etc.
44
-
45
- class Error < StandardError
46
- end
47
-
48
- # ConfigurationError
49
- #
50
- # An exception raised when there is a problem with Base.connect arguments
51
- class ConfigurationError < Error
52
- end
53
-
54
- # DeleteError
55
- #
56
- # An exception raised when an ActiveLdap delete action fails
57
- class DeleteError < Error
58
- end
59
-
60
- # SaveError
61
- #
62
- # An exception raised when an ActiveLdap save action failes
63
- class SaveError < Error
64
- end
65
-
66
- # AuthenticationError
67
- #
68
- # An exception raised when user authentication fails
69
- class AuthenticationError < Error
70
- end
71
-
72
- # ConnectionError
73
- #
74
- # An exception raised when the LDAP conenction fails
75
- class ConnectionError < Error
76
- end
77
-
78
- # ObjectClassError
79
- #
80
- # An exception raised when an objectClass is not defined in the schema
81
- class ObjectClassError < Error
82
- end
83
-
84
- # AttributeAssignmentError
85
- #
86
- # An exception raised when there is an issue assigning a value to
87
- # an attribute
88
- class AttributeAssignmentError < Error
89
- end
90
-
91
- # TimeoutError
92
- #
93
- # An exception raised when a connection action fails due to a timeout
94
- class TimeoutError < Error
95
- end
96
-
97
- class EntryNotFound < Error
98
- end
99
-
100
- class EntryAlreadyExist < Error
101
- end
102
-
103
- class StrongAuthenticationRequired < Error
104
- end
105
-
106
- class DistinguishedNameInvalid < Error
107
- attr_reader :dn, :reason
108
- def initialize(dn, reason=nil)
109
- @dn = dn
110
- @reason = reason
111
- message = "#{@dn} is invalid distinguished name (dn)"
112
- message << ": #{@reason}"if @reason
113
- super(message)
114
- end
115
- end
116
-
117
- class DistinguishedNameNotSetError < Error
118
- end
119
-
120
- class EntryNotSaved < Error
121
- end
122
-
123
- class RequiredObjectClassMissed < Error
124
- end
125
-
126
- class RequiredAttributeMissed < Error
127
- end
128
-
129
- class EntryInvalid < Error
130
- end
131
-
132
- class OperationNotPermitted < Error
133
- end
134
-
135
- class ConnectionNotEstablished < Error
136
- end
137
-
138
- class AdapterNotSpecified < Error
139
- end
140
-
141
- class AdapterNotFound < Error
142
- attr_reader :adapter
143
- def initialize(adapter)
144
- @adapter = adapter
145
- super("LDAP configuration specifies nonexistent #{@adapter} adapter")
146
- end
147
- end
148
-
149
- class UnknownAttribute < Error
150
- attr_reader :name
151
- def initialize(name)
152
- @name = name
153
- super("#{@name} is unknown attribute")
154
- end
155
- end
156
-
157
- # Base
158
- #
159
- # Base is the primary class which contains all of the core
160
- # ActiveLdap functionality. It is meant to only ever be subclassed
161
- # by extension classes.
162
- class Base
163
- if Reloadable.const_defined?(:Deprecated)
164
- include Reloadable::Deprecated
165
- else
166
- include Reloadable::Subclasses
167
- end
168
-
169
- VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :scope,
170
- :classes, :recommended_classes]
171
- VALID_SEARCH_OPTIONS = [:attribute, :value, :filter, :prefix,
172
- :classes, :scope, :limit, :attributes,
173
- :sort_by, :order]
174
-
175
- cattr_accessor :logger
176
- cattr_accessor :configurations
177
- @@configurations = {}
178
-
179
- def self.class_local_attr_accessor(search_ancestors, *syms)
180
- syms.flatten.each do |sym|
181
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
182
- def self.#{sym}(search_superclasses=#{search_ancestors})
183
- @#{sym} ||= nil
184
- return @#{sym} if @#{sym}
185
- if search_superclasses
186
- target = superclass
187
- value = nil
188
- loop do
189
- break nil unless target.respond_to?("#{sym}")
190
- value = target.#{sym}
191
- break if value
192
- target = target.superclass
193
- end
194
- value
195
- else
196
- nil
197
- end
198
- end
199
- def #{sym}; self.class.#{sym}; end
200
- def self.#{sym}=(value); @#{sym} = value; end
201
- def #{sym}=(value); self.class.#{sym} = value; end
202
- EOS
203
- end
204
- end
205
-
206
- class_local_attr_accessor false, :prefix, :base, :dn_attribute
207
- class_local_attr_accessor true, :ldap_scope
208
- class_local_attr_accessor true, :required_classes, :recommended_classes
209
-
210
- class << self
211
- # Hide new in Base
212
- private :new
213
-
214
- # Connect and bind to LDAP creating a class variable for use by
215
- # all ActiveLdap objects.
216
- #
217
- # == +config+
218
- # +config+ must be a hash that may contain any of the following fields:
219
- # :password_block, :logger, :host, :port, :base, :bind_dn,
220
- # :try_sasl, :allow_anonymous
221
- # :bind_dn specifies the DN to bind with.
222
- # :password_block specifies a Proc object that will yield a String to
223
- # be used as the password when called.
224
- # :logger specifies a preconfigured Log4r::Logger to be used for all
225
- # logging
226
- # :host sets the LDAP server hostname
227
- # :port sets the LDAP server port
228
- # :base overwrites Base.base - this affects EVERYTHING
229
- # :try_sasl indicates that a SASL bind should be attempted when binding
230
- # to the server (default: false)
231
- # :sasl_mechanisms is an array of SASL mechanism to try
232
- # (default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
233
- # :allow_anonymous indicates that a true anonymous bind is allowed when
234
- # trying to bind to the server (default: true)
235
- # :retries - indicates the number of attempts to reconnect that will be
236
- # undertaken when a stale connection occurs. -1 means infinite.
237
- # :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
238
- # :method - whether to use :ssl, :tls, or :plain (unencrypted)
239
- # :retry_wait - seconds to wait before retrying a connection
240
- # :ldap_scope - dictates how to find objects. ONELEVEL by default to
241
- # avoid dn_attr collisions across OUs. Think before changing.
242
- # :timeout - time in seconds - defaults to disabled. This CAN interrupt
243
- # search() requests. Be warned.
244
- # :retry_on_timeout - whether to reconnect when timeouts occur. Defaults
245
- # to true
246
- # See lib/configuration.rb for defaults for each option
247
- def establish_connection(config=nil)
248
- super
249
- ensure_logger
250
- connection.connect
251
- # Make irb users happy with a 'true'
252
- true
253
- end
254
-
255
- def create(attributes=nil, &block)
256
- if attributes.is_a?(Array)
257
- attributes.collect {|attrs| create(attrs, &block)}
258
- else
259
- object = new(attributes, &block)
260
- object.save
261
- object
262
- end
263
- end
264
-
265
- def search(options={}, &block)
266
- validate_search_options(options)
267
- attr = options[:attribute]
268
- value = options[:value] || '*'
269
- filter = options[:filter]
270
- prefix = options[:prefix]
271
- classes = options[:classes]
272
-
273
- value = value.first if value.is_a?(Array) and value.first.size == 1
274
- if filter.nil? and !value.is_a?(String)
275
- raise ArgumentError, "Search value must be a String"
276
- end
277
-
278
- _attr, value, _prefix = split_search_value(value)
279
- attr ||= _attr || dn_attribute || "objectClass"
280
- prefix ||= _prefix
281
- if filter.nil?
282
- filter = "(#{attr}=#{escape_filter_value(value, true)})"
283
- filter = "(&#{filter}#{object_class_filters(classes)})"
284
- end
285
- _base = [prefix, base].compact.reject{|x| x.empty?}.join(",")
286
- search_options = {
287
- :base => _base,
288
- :scope => options[:scope] || ldap_scope,
289
- :filter => filter,
290
- :limit => options[:limit],
291
- :attributes => options[:attributes],
292
- :sort_by => options[:sort_by],
293
- :order => options[:order],
294
- }
295
- connection.search(search_options) do |dn, attrs|
296
- attributes = {}
297
- attrs.each do |key, value|
298
- normalized_attr, normalized_value = make_subtypes(key, value)
299
- attributes[normalized_attr] ||= []
300
- attributes[normalized_attr].concat(normalized_value)
301
- end
302
- value = [dn, attributes]
303
- value = yield(value) if block_given?
304
- value
305
- end
306
- end
307
-
308
- # This class function is used to setup all mappings between the subclass
309
- # and ldap for use in activeldap
310
- #
311
- # Example:
312
- # ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
313
- # :classes => ['top', 'posixAccount'],
314
- # :scope => :sub
315
- def ldap_mapping(options={})
316
- validate_ldap_mapping_options(options)
317
- dn_attribute = options[:dn_attribute] || default_dn_attribute
318
- prefix = options[:prefix] || default_prefix
319
- classes = options[:classes]
320
- recommended_classes = options[:recommended_classes]
321
- scope = options[:scope]
322
-
323
- self.dn_attribute = dn_attribute
324
- self.prefix = prefix
325
- self.ldap_scope = scope
326
- self.required_classes = classes
327
- self.recommended_classes = recommended_classes
328
-
329
- public_class_method :new
330
- end
331
-
332
- alias_method :base_inheritable, :base
333
- # Base.base
334
- #
335
- # This method when included into Base provides
336
- # an inheritable, overwritable configuration setting
337
- #
338
- # This should be a string with the base of the
339
- # ldap server such as 'dc=example,dc=com', and
340
- # it should be overwritten by including
341
- # configuration.rb into this class.
342
- # When subclassing, the specified prefix will be concatenated.
343
- def base
344
- _base = base_inheritable
345
- _base = configuration[:base] if _base.nil? and configuration
346
- _base ||= base_inheritable(true)
347
- [prefix, _base].find_all do |component|
348
- component and !component.empty?
349
- end.join(",")
350
- end
351
-
352
- alias_method :ldap_scope_without_validation=, :ldap_scope=
353
- def ldap_scope=(scope)
354
- scope = scope.to_sym if scope.is_a?(String)
355
- if scope.nil? or scope.is_a?(Symbol)
356
- self.ldap_scope_without_validation = scope
357
- else
358
- raise ConfigurationError,
359
- ":ldap_scope '#{scope.inspect}' must be a Symbol"
360
- end
361
- end
362
-
363
- def dump(options={})
364
- ldifs = []
365
- options = {:base => base, :scope => ldap_scope}.merge(options)
366
- connection.search(options) do |dn, attributes|
367
- ldifs << to_ldif(dn, attributes)
368
- end
369
- ldifs.join("\n")
370
- end
371
-
372
- def to_ldif(dn, attributes)
373
- connection.to_ldif(dn, unnormalize_attributes(attributes))
374
- end
375
-
376
- def load(ldifs)
377
- connection.load(ldifs)
378
- end
379
-
380
- def destroy(targets, options={})
381
- targets = [targets] unless targets.is_a?(Array)
382
- targets.each do |target|
383
- find(target, options).destroy
384
- end
385
- end
386
-
387
- def destroy_all(filter=nil, options={})
388
- targets = []
389
- if filter.is_a?(Hash)
390
- options = options.merge(filter)
391
- filter = nil
392
- end
393
- options = options.merge(:filter => filter) if filter
394
- find(:all, options).sort_by do |target|
395
- target.dn.reverse
396
- end.reverse.each do |target|
397
- target.destroy
398
- end
399
- end
400
-
401
- def delete(targets, options={})
402
- targets = [targets] unless targets.is_a?(Array)
403
- targets = targets.collect do |target|
404
- ensure_dn_attribute(ensure_base(target))
405
- end
406
- connection.delete(targets, options)
407
- end
408
-
409
- def delete_all(filter=nil, options={})
410
- options = {:base => base, :scope => ldap_scope}.merge(options)
411
- options = options.merge(:filter => filter) if filter
412
- targets = connection.search(options).collect do |dn, attributes|
413
- dn
414
- end.sort_by do |dn|
415
- dn.reverse
416
- end.reverse
417
-
418
- connection.delete(targets)
419
- end
420
-
421
- def add(dn, entries, options={})
422
- unnormalized_entries = entries.collect do |type, key, value|
423
- [type, key, unnormalize_attribute(key, value)]
424
- end
425
- connection.add(dn, unnormalized_entries, options)
426
- end
427
-
428
- def modify(dn, entries, options={})
429
- unnormalized_entries = entries.collect do |type, key, value|
430
- [type, key, unnormalize_attribute(key, value)]
431
- end
432
- connection.modify(dn, unnormalized_entries, options)
433
- end
434
-
435
- # find
436
- #
437
- # Finds the first match for value where |value| is the value of some
438
- # |field|, or the wildcard match. This is only useful for derived classes.
439
- # usage: Subclass.find(:attribute => "cn", :value => "some*val")
440
- # Subclass.find('some*val')
441
- def find(*args)
442
- options = extract_options_from_args!(args)
443
- args = [:first] if args.empty? and !options.empty?
444
- case args.first
445
- when :first
446
- find_initial(options)
447
- when :all
448
- find_every(options)
449
- else
450
- find_from_dns(args, options)
451
- end
452
- end
453
-
454
- def exists?(dn, options={})
455
- prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/ #
456
- dn_suffix = nil
457
- not search({:value => dn}.merge(options)).find do |_dn,|
458
- if prefix.match(_dn)
459
- begin
460
- dn_suffix ||= DN.parse(base)
461
- dn_prefix = DN.parse(_dn) - dn_suffix
462
- true
463
- rescue DistinguishedNameInvalid, ArgumentError
464
- false
465
- end
466
- else
467
- false
468
- end
469
- end.nil?
470
- end
471
-
472
- def update(dn, attributes, options={})
473
- if dn.is_a?(Array)
474
- i = -1
475
- dns = dn
476
- dns.collect do |dn|
477
- i += 1
478
- update(dn, attributes[i], options)
479
- end
480
- else
481
- object = find(dn, options)
482
- object.update_attributes(attributes)
483
- object
484
- end
485
- end
486
-
487
- def update_all(attributes, filter=nil, options={})
488
- search_options = options
489
- if filter
490
- if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter
491
- search_options = search_options.merge(:value => filter)
492
- else
493
- search_options = search_options.merge(:filter => filter)
494
- end
495
- end
496
- targets = search(search_options).collect do |dn, attrs|
497
- dn
498
- end
499
-
500
- entries = attributes.collect do |name, value|
501
- normalized_name, normalized_value = normalize_attribute(name, value)
502
- [:replace, normalized_name,
503
- unnormalize_attribute(normalized_name, normalized_value)]
504
- end
505
- targets.each do |dn|
506
- connection.modify(dn, entries, options)
507
- end
508
- end
509
-
510
- def base_class
511
- if self == Base or superclass == Base
512
- self
513
- else
514
- superclass.base_class
515
- end
516
- end
517
-
518
- def human_attribute_name(attribute_key_name)
519
- attribute_key_name.humanize
520
- end
521
-
522
- private
523
- def validate_ldap_mapping_options(options)
524
- options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
525
- end
526
-
527
- def validate_search_options(options)
528
- options.assert_valid_keys(VALID_SEARCH_OPTIONS)
529
- end
530
-
531
- def extract_options_from_args!(args)
532
- args.last.is_a?(Hash) ? args.pop : {}
533
- end
534
-
535
- def object_class_filters(classes=nil)
536
- (classes || required_classes).collect do |name|
537
- "(objectClass=#{escape_filter_value(name, true)})"
538
- end.join("")
539
- end
540
-
541
- def find_initial(options)
542
- find_every(options.merge(:limit => 1)).first
543
- end
544
-
545
- def normalize_sort_order(value)
546
- case value.to_s
547
- when /\Aasc(?:end)?\z/i
548
- :ascend
549
- when /\Adesc(?:end)?\z/i
550
- :descend
551
- else
552
- raise ArgumentError, "Invalid order: #{value.inspect}"
553
- end
554
- end
555
-
556
- def find_every(options)
557
- options = options.dup
558
- sort_by = options.delete(:sort_by)
559
- order = options.delete(:order)
560
- limit = options.delete(:limit) if sort_by or order
561
-
562
- results = search(options).collect do |dn, attrs|
563
- instantiate([dn, attrs])
564
- end
565
- return results if sort_by.nil? and order.nil?
566
-
567
- sort_by ||= "dn"
568
- if sort_by.downcase == "dn"
569
- results = results.sort_by {|result| DN.parse(result.dn)}
570
- else
571
- results = results.sort_by {|result| result.send(sort_by)}
572
- end
573
-
574
- results.reverse! if normalize_sort_order(order || "ascend") == :descend
575
- results = results[0, limit] if limit
576
- results
577
- end
578
-
579
- def find_from_dns(dns, options)
580
- expects_array = dns.first.is_a?(Array)
581
- return [] if expects_array and dns.first.empty?
582
-
583
- dns = dns.flatten.compact.uniq
584
-
585
- case dns.size
586
- when 0
587
- raise EntryNotFound, "Couldn't find #{name} without a DN"
588
- when 1
589
- result = find_one(dns.first, options)
590
- expects_array ? [result] : result
591
- else
592
- find_some(dns, options)
593
- end
594
- end
595
-
596
- def find_one(dn, options)
597
- attr, value, prefix = split_search_value(dn)
598
- filters = [
599
- "(#{attr || dn_attribute}=#{escape_filter_value(value, true)})",
600
- object_class_filters(options[:classes]),
601
- options[:filter],
602
- ]
603
- filter = "(&#{filters.compact.join('')})"
604
- options = {:prefix => prefix}.merge(options.merge(:filter => filter))
605
- result = find_initial(options)
606
- if result
607
- result
608
- else
609
- message = "Couldn't find #{name} with DN=#{dn}"
610
- message << " #{options[:filter]}" if options[:filter]
611
- raise EntryNotFound, message
612
- end
613
- end
614
-
615
- def find_some(dns, options)
616
- dn_filters = dns.collect do |dn|
617
- attr, value, prefix = split_search_value(dn)
618
- attr ||= dn_attribute
619
- filter = "(#{attr}=#{escape_filter_value(value, true)})"
620
- if prefix
621
- filter = "(&#{filter}(dn=*,#{escape_filter_value(prefix)},#{base}))"
622
- end
623
- filter
624
- end
625
- filters = [
626
- "(|#{dn_filters.join('')})",
627
- object_class_filters(options[:classes]),
628
- options[:filter],
629
- ]
630
- filter = "(&#{filters.compact.join('')})"
631
- result = find_every(options.merge(:filter => filter))
632
- if result.size == dns.size
633
- result
634
- else
635
- message = "Couldn't find all #{name} with DNs (#{dns.join(', ')})"
636
- message << " #{options[:filter]}"if options[:filter]
637
- raise EntryNotFound, message
638
- end
639
- end
640
-
641
- def split_search_value(value)
642
- attr = prefix = nil
643
- begin
644
- dn = DN.parse(value)
645
- attr, value = dn.rdns.first.to_a.first
646
- rest = dn.rdns[1..-1]
647
- prefix = DN.new(*rest).to_s unless rest.empty?
648
- rescue DistinguishedNameInvalid
649
- begin
650
- dn = DN.parse("DUMMY=#{value}")
651
- _, value = dn.rdns.first.to_a.first
652
- rest = dn.rdns[1..-1]
653
- prefix = DN.new(*rest).to_s unless rest.empty?
654
- rescue DistinguishedNameInvalid
655
- end
656
- end
657
-
658
- prefix = nil if prefix == base
659
- prefix = truncate_base(prefix) if prefix
660
- [attr, value, prefix]
661
- end
662
-
663
- def escape_filter_value(value, without_asterisk=false)
664
- value.gsub(/[\*\(\)\\\0]/) do |x|
665
- if without_asterisk and x == "*"
666
- x
667
- else
668
- "\\%02x" % x[0]
669
- end
670
- end
671
- end
672
-
673
- def ensure_dn(target)
674
- attr, value, prefix = split_search_value(target)
675
- "#{attr || dn_attribute}=#{value},#{prefix || base}"
676
- end
677
-
678
- def ensure_dn_attribute(target)
679
- "#{dn_attribute}=" +
680
- target.gsub(/^\s*#{Regexp.escape(dn_attribute)}\s*=\s*/i, '')
681
- end
682
-
683
- def ensure_base(target)
684
- [truncate_base(target), base].join(',')
685
- end
686
-
687
- def truncate_base(target)
688
- if /,/ =~ target
689
- begin
690
- (DN.parse(target) - DN.parse(base)).to_s
691
- rescue DistinguishedNameInvalid, ArgumentError
692
- target
693
- end
694
- else
695
- target
696
- end
697
- end
698
-
699
- def ensure_logger
700
- @@logger ||= configuration[:logger]
701
- # Setup default logger to console
702
- if @@logger.nil?
703
- require 'log4r'
704
- @@logger = Log4r::Logger.new('activeldap')
705
- @@logger.level = Log4r::OFF
706
- Log4r::StderrOutputter.new 'console'
707
- @@logger.add('console')
708
- end
709
- configuration[:logger] ||= @@logger
710
- end
711
-
712
- def instantiate(entry)
713
- dn, attributes = entry
714
- if self.class == Class
715
- klass = self.ancestors[0].to_s.split(':').last
716
- real_klass = self.ancestors[0]
717
- else
718
- klass = self.class.to_s.split(':').last
719
- real_klass = self.class
720
- end
721
-
722
- obj = real_klass.allocate
723
- obj.instance_eval do
724
- initialize_by_ldap_data(dn, attributes)
725
- end
726
- obj
727
- end
728
-
729
- def default_dn_attribute
730
- if name.empty?
731
- dn_attribute = nil
732
- parent_class = ancestors[1]
733
- if parent_class.respond_to?(:dn_attribute)
734
- dn_attribute = parent_class.dn_attribute
735
- end
736
- dn_attribute || "cn"
737
- else
738
- Inflector.underscore(Inflector.demodulize(name))
739
- end
740
- end
741
-
742
- def default_prefix
743
- if name.empty?
744
- nil
745
- else
746
- "ou=#{Inflector.pluralize(Inflector.demodulize(name))}"
747
- end
748
- end
749
- end
750
-
751
- self.ldap_scope = :sub
752
- self.required_classes = ['top']
753
- self.recommended_classes = []
754
-
755
- include Enumerable
756
-
757
- ### All instance methods, etc
758
-
759
- # new
760
- #
761
- # Creates a new instance of Base initializing all class and all
762
- # initialization. Defines local defaults. See examples If multiple values
763
- # exist for dn_attribute, the first one put here will be authoritative
764
- def initialize(attributes=nil)
765
- init_base
766
- @new_entry = true
767
- initial_classes = required_classes | recommended_classes
768
- if attributes.nil?
769
- apply_object_class(initial_classes)
770
- elsif attributes.is_a?(String) or attributes.is_a?(Array)
771
- apply_object_class(initial_classes)
772
- self.dn = attributes
773
- elsif attributes.is_a?(Hash)
774
- classes, attributes = extract_object_class(attributes)
775
- apply_object_class(classes | initial_classes)
776
- normalized_attributes = {}
777
- attributes.each do |key, value|
778
- real_key = to_real_attribute_name(key)
779
- normalized_attributes[real_key] = value if real_key
780
- end
781
- self.dn = normalized_attributes[dn_attribute]
782
- self.attributes = normalized_attributes
783
- else
784
- message = "'#{attributes.inspect}' must be either "
785
- message << "nil, DN value as String or Array or attributes as Hash"
786
- raise ArgumentError, message
787
- end
788
- yield self if block_given?
789
- end
790
-
791
- # Returns true if the +comparison_object+ is the same object, or is of
792
- # the same type and has the same dn.
793
- def ==(comparison_object)
794
- comparison_object.equal?(self) or
795
- (comparison_object.instance_of?(self.class) and
796
- comparison_object.dn == dn and
797
- !comparison_object.new_entry?)
798
- end
799
-
800
- # Delegates to ==
801
- def eql?(comparison_object)
802
- self == (comparison_object)
803
- end
804
-
805
- # Delegates to id in order to allow two records of the same type and id
806
- # to work with something like:
807
- # [ User.find("a"), User.find("b"), User.find("c") ] &
808
- # [ User.find("a"), User.find("d") ] # => [ User.find("a") ]
809
- def hash
810
- dn.hash
811
- end
812
-
813
- def may
814
- ensure_apply_object_class
815
- @may
816
- end
817
-
818
- def must
819
- ensure_apply_object_class
820
- @must
821
- end
822
-
823
- # attributes
824
- #
825
- # Return attribute methods so that a program can determine available
826
- # attributes dynamically without schema awareness
827
- def attribute_names
828
- logger.debug {"stub: attribute_names called"}
829
- ensure_apply_object_class
830
- return @attr_methods.keys
831
- end
832
-
833
- def attribute_present?(name)
834
- values = get_attribute(name, true)
835
- !values.empty? or values.any? {|x| not (x and x.empty?)}
836
- end
837
-
838
- # exists?
839
- #
840
- # Return whether the entry exists in LDAP or not
841
- def exists?
842
- self.class.exists?(dn)
843
- end
844
-
845
- # new_entry?
846
- #
847
- # Return whether the entry is new entry in LDAP or not
848
- def new_entry?
849
- @new_entry
850
- end
851
-
852
- # dn
853
- #
854
- # Return the authoritative dn
855
- def dn
856
- logger.debug {"stub: dn called"}
857
- dn_value = id
858
- if dn_value.nil?
859
- raise DistinguishedNameNotSetError.new,
860
- "#{dn_attribute} value of #{self} doesn't set"
861
- end
862
- _base = base
863
- _base = nil if _base.empty?
864
- ["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
865
- end
866
-
867
- def id
868
- get_attribute(dn_attribute)
869
- end
870
-
871
- def to_param
872
- id
873
- end
874
-
875
- def dn=(value)
876
- set_attribute(dn_attribute, value)
877
- end
878
- alias_method(:id=, :dn=)
879
-
880
- # destroy
881
- #
882
- # Delete this entry from LDAP
883
- def destroy
884
- logger.debug {"stub: delete called"}
885
- begin
886
- self.class.delete(dn)
887
- @new_entry = true
888
- rescue Error
889
- raise DeleteError.new("Failed to delete LDAP entry: '#{dn}'")
890
- end
891
- end
892
-
893
- # save
894
- #
895
- # Save and validate this object into LDAP
896
- # either adding or replacing attributes
897
- # TODO: Relative DN support
898
- def save
899
- create_or_update
900
- end
901
-
902
- def save!
903
- unless create_or_update
904
- raise EntryNotSaved, "entry #{dn} can't saved"
905
- end
906
- end
907
-
908
- # method_missing
909
- #
910
- # If a given method matches an attribute or an attribute alias
911
- # then call the appropriate method.
912
- # TODO: Determine if it would be better to define each allowed method
913
- # using class_eval instead of using method_missing. This would
914
- # give tab completion in irb.
915
- def method_missing(name, *args, &block)
916
- logger.debug {"stub: called method_missing" +
917
- "(#{name.inspect}, #{args.inspect})"}
918
- ensure_apply_object_class
919
-
920
- key = name.to_s
921
- case key
922
- when /=$/
923
- real_key = $PREMATCH
924
- logger.debug {"method_missing: have_attribute? #{real_key}"}
925
- if have_attribute?(real_key, ['objectClass'])
926
- if args.size != 1
927
- raise ArgumentError,
928
- "wrong number of arguments (#{args.size} for 1)"
929
- end
930
- logger.debug {"method_missing: calling set_attribute" +
931
- "(#{real_key}, #{args.inspect})"}
932
- return set_attribute(real_key, *args, &block)
933
- end
934
- when /(?:(_before_type_cast)|(\?))?$/
935
- real_key = $PREMATCH
936
- before_type_cast = !$1.nil?
937
- query = !$2.nil?
938
- logger.debug {"method_missing: have_attribute? #{real_key}"}
939
- if have_attribute?(real_key, ['objectClass'])
940
- if args.size > 1
941
- raise ArgumentError,
942
- "wrong number of arguments (#{args.size} for 1)"
943
- end
944
- if before_type_cast
945
- return get_attribute_before_type_cast(real_key, *args)
946
- elsif query
947
- return get_attribute_as_query(real_key, *args)
948
- else
949
- return get_attribute(real_key, *args)
950
- end
951
- end
952
- end
953
- super
954
- end
955
-
956
- # Add available attributes to the methods
957
- def methods(inherited_too=true)
958
- ensure_apply_object_class
959
- target_names = @attr_methods.keys + @attr_aliases.keys
960
- target_names -= ['objectClass', Inflector.underscore('objectClass')]
961
- super + target_names.uniq.collect do |x|
962
- [x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
963
- end.flatten
964
- end
965
-
966
- alias_method :respond_to_without_attributes?, :respond_to?
967
- def respond_to?(name, include_priv=false)
968
- have_attribute?(name.to_s) or
969
- (/(?:=|\?|_before_type_cast)$/ =~ name.to_s and
970
- have_attribute?($PREMATCH)) or
971
- super
972
- end
973
-
974
- # Updates a given attribute and saves immediately
975
- def update_attribute(name, value)
976
- set_attribute(name, value) if have_attribute?(name)
977
- save
978
- end
979
-
980
- # This performs a bulk update of attributes and immediately
981
- # calls #save.
982
- def update_attributes(attrs)
983
- self.attributes = attrs
984
- save
985
- end
986
-
987
- # This returns the key value pairs in @data with all values
988
- # cloned
989
- def attributes
990
- Marshal.load(Marshal.dump(@data))
991
- end
992
-
993
- # This allows a bulk update to the attributes of a record
994
- # without forcing an immediate save or validation.
995
- #
996
- # It is unwise to attempt objectClass updates this way.
997
- # Also be sure to only pass in key-value pairs of your choosing.
998
- # Do not let URL/form hackers supply the keys.
999
- def attributes=(hash_or_assoc)
1000
- targets = remove_attributes_protected_from_mass_assignment(hash_or_assoc)
1001
- targets.each do |key, value|
1002
- set_attribute(key, value) if have_attribute?(key)
1003
- end
1004
- end
1005
-
1006
- def to_ldif
1007
- self.class.to_ldif(dn, normalize_data(@data))
1008
- end
1009
-
1010
- def to_xml(options={})
1011
- root = options[:root] || Inflector.underscore(self.class.name)
1012
- result = "<#{root}>\n"
1013
- result << " <dn>#{dn}</dn>\n"
1014
- normalize_data(@data).sort_by {|key, values| key}.each do |key, values|
1015
- targets = []
1016
- values.each do |value|
1017
- if value.is_a?(Hash)
1018
- value.each do |option, real_value|
1019
- targets << [real_value, " #{option}=\"true\""]
1020
- end
1021
- else
1022
- targets << [value]
1023
- end
1024
- end
1025
- targets.sort_by {|value, attr| value}.each do |value, attr|
1026
- result << " <#{key}#{attr}>#{value}</#{key}>\n"
1027
- end
1028
- end
1029
- result << "</#{root}>\n"
1030
- result
1031
- end
1032
-
1033
- def have_attribute?(name, except=[])
1034
- real_name = to_real_attribute_name(name)
1035
- real_name and !except.include?(real_name)
1036
- end
1037
- alias_method :has_attribute?, :have_attribute?
1038
-
1039
- def reload
1040
- _, attributes = self.class.search(:value => id).find do |_dn, _attributes|
1041
- dn == _dn
1042
- end
1043
- raise EntryNotFound, "Can't find dn '#{dn}' to reload" if attributes.nil?
1044
-
1045
- @ldap_data.update(attributes)
1046
- classes, attributes = extract_object_class(attributes)
1047
- apply_object_class(classes)
1048
- self.attributes = attributes
1049
- @new_entry = false
1050
- self
1051
- end
1052
-
1053
- def [](name, force_array=false)
1054
- if name == "dn"
1055
- array_of(dn, force_array)
1056
- else
1057
- get_attribute(name, force_array)
1058
- end
1059
- end
1060
-
1061
- def []=(name, value)
1062
- set_attribute(name, value)
1063
- end
1064
-
1065
- def each
1066
- @data.each do |key, values|
1067
- yield(key.dup, values.dup)
1068
- end
1069
- end
1070
-
1071
- private
1072
- def extract_object_class(attributes)
1073
- classes = []
1074
- attrs = attributes.reject do |key, value|
1075
- key = key.to_s
1076
- if key == 'objectClass' or
1077
- key.underscore == 'object_class' or
1078
- key.downcase == 'objectclass'
1079
- classes |= [value].flatten
1080
- true
1081
- else
1082
- false
1083
- end
1084
- end
1085
- [classes, attrs]
1086
- end
1087
-
1088
- def init_base
1089
- check_configuration
1090
- init_instance_variables
1091
- end
1092
-
1093
- def initialize_by_ldap_data(dn, attributes)
1094
- init_base
1095
- @new_entry = false
1096
- @ldap_data = attributes
1097
- classes, attributes = extract_object_class(attributes)
1098
- apply_object_class(classes)
1099
- self.dn = dn
1100
- self.attributes = attributes
1101
- yield self if block_given?
1102
- end
1103
-
1104
- def to_real_attribute_name(name, allow_normalized_name=false)
1105
- ensure_apply_object_class
1106
- name = name.to_s
1107
- real_name = @attr_methods[name]
1108
- real_name ||= @attr_aliases[Inflector.underscore(name)]
1109
- if real_name
1110
- real_name
1111
- elsif allow_normalized_name
1112
- @attr_methods[@normalized_attr_names[normalize_attribute_name(name)]]
1113
- else
1114
- nil
1115
- end
1116
- end
1117
-
1118
- def ensure_apply_object_class
1119
- current_object_class = @data['objectClass']
1120
- return if current_object_class.nil? or current_object_class == @last_oc
1121
- apply_object_class(current_object_class)
1122
- end
1123
-
1124
- # enforce_type
1125
- #
1126
- # enforce_type applies your changes without attempting to write to LDAP.
1127
- # This means that if you set userCertificate to somebinary value, it will
1128
- # wrap it up correctly.
1129
- def enforce_type(key, value)
1130
- logger.debug {"stub: enforce_type called"}
1131
- ensure_apply_object_class
1132
- # Enforce attribute value formatting
1133
- result = self.class.normalize_attribute(key, value)[1]
1134
- logger.debug {"stub: enforce_types done"}
1135
- result
1136
- end
1137
-
1138
- def init_instance_variables
1139
- @data = {} # where the r/w entry data is stored
1140
- @ldap_data = {} # original ldap entry data
1141
- @attr_methods = {} # list of valid method calls for attributes used for
1142
- # dereferencing
1143
- @normalized_attr_names = {} # list of normalized attribute name
1144
- @attr_aliases = {} # aliases of @attr_methods
1145
- @last_oc = false # for use in other methods for "caching"
1146
- @base = nil
1147
- end
1148
-
1149
- # apply_object_class
1150
- #
1151
- # objectClass= special case for updating appropriately
1152
- # This updates the objectClass entry in @data. It also
1153
- # updating all required and allowed attributes while
1154
- # removing defined attributes that are no longer valid
1155
- # given the new objectclasses.
1156
- def apply_object_class(val)
1157
- logger.debug {"stub: objectClass=(#{val.inspect}) called"}
1158
- new_oc = val
1159
- new_oc = [val] if new_oc.class != Array
1160
- new_oc = new_oc.uniq
1161
- return new_oc if @last_oc == new_oc
1162
-
1163
- # Store for caching purposes
1164
- @last_oc = new_oc.dup
1165
-
1166
- # Set the actual objectClass data
1167
- define_attribute_methods('objectClass')
1168
- replace_class(*new_oc)
1169
-
1170
- # Build |data| from schema
1171
- # clear attr_method mapping first
1172
- @attr_methods = {}
1173
- @normalized_attr_names = {}
1174
- @attr_aliases = {}
1175
- @musts = {}
1176
- @mays = {}
1177
- new_oc.each do |objc|
1178
- # get all attributes for the class
1179
- attributes = schema.class_attributes(objc)
1180
- @musts[objc] = attributes[:must]
1181
- @mays[objc] = attributes[:may]
1182
- end
1183
- @must = normalize_attribute_names(@musts.values)
1184
- @may = normalize_attribute_names(@mays.values)
1185
- (@must + @may).uniq.each do |attr|
1186
- # Update attr_method with appropriate
1187
- define_attribute_methods(attr)
1188
- end
1189
- end
1190
-
1191
- def normalize_attribute_names(names)
1192
- names.flatten.uniq.collect do |may|
1193
- schema.attribute_aliases(may).first
1194
- end
1195
- end
1196
-
1197
- alias_method :base_of_class, :base
1198
- def base
1199
- logger.debug {"stub: called base"}
1200
- [@base, base_of_class].compact.join(",")
1201
- end
1202
-
1203
- undef_method :base=
1204
- def base=(object_local_base)
1205
- @base = object_local_base
1206
- end
1207
-
1208
- # get_attribute
1209
- #
1210
- # Return the value of the attribute called by method_missing?
1211
- def get_attribute(name, force_array=false)
1212
- logger.debug {"stub: called get_attribute" +
1213
- "(#{name.inspect}, #{force_array.inspect}"}
1214
- get_attribute_before_type_cast(name, force_array)
1215
- end
1216
-
1217
- def get_attribute_as_query(name, force_array=false)
1218
- logger.debug {"stub: called get_attribute_as_query" +
1219
- "(#{name.inspect}, #{force_array.inspect}"}
1220
- value = get_attribute_before_type_cast(name, force_array)
1221
- if force_array
1222
- value.collect {|x| !false_value?(x)}
1223
- else
1224
- !false_value?(value)
1225
- end
1226
- end
1227
-
1228
- def false_value?(value)
1229
- value.nil? or value == false or value == [] or
1230
- value == "false" or value == "FALSE" or value == ""
1231
- end
1232
-
1233
- def get_attribute_before_type_cast(name, force_array=false)
1234
- logger.debug {"stub: called get_attribute_before_type_cast" +
1235
- "(#{name.inspect}, #{force_array.inspect}"}
1236
- attr = to_real_attribute_name(name)
1237
-
1238
- value = @data[attr] || []
1239
- # Return a copy of the stored data
1240
- if force_array
1241
- value.dup
1242
- else
1243
- array_of(value.dup, false)
1244
- end
1245
- end
1246
-
1247
- # set_attribute
1248
- #
1249
- # Set the value of the attribute called by method_missing?
1250
- def set_attribute(name, value)
1251
- logger.debug {"stub: called set_attribute" +
1252
- "(#{name.inspect}, #{value.inspect})"}
1253
-
1254
- # Get the attr and clean up the input
1255
- attr = to_real_attribute_name(name)
1256
- raise UnknownAttribute.new(name) if attr.nil?
1257
-
1258
- if attr == dn_attribute and value.is_a?(String)
1259
- value, @base = split_dn_value(value)
1260
- end
1261
-
1262
- logger.debug {"set_attribute(#{name.inspect}, #{value.inspect}): " +
1263
- "method maps to #{attr}"}
1264
-
1265
- # Enforce LDAP-pleasing values
1266
- logger.debug {"value = #{value.inspect}, value.class = #{value.class}"}
1267
- real_value = value
1268
- # Squash empty values
1269
- if value.class == Array
1270
- real_value = value.collect {|c| (c.nil? or c.empty?) ? [] : c}.flatten
1271
- end
1272
- real_value = [] if real_value.nil?
1273
- real_value = [] if real_value == ''
1274
- real_value = [real_value] if real_value.class == String
1275
- real_value = [real_value.to_s] if real_value.class == Fixnum
1276
- # NOTE: Hashes are allowed for subtyping.
1277
-
1278
- # Assign the value
1279
- @data[attr] = enforce_type(attr, real_value)
1280
-
1281
- # Return the passed in value
1282
- logger.debug {"stub: exiting set_attribute"}
1283
- @data[attr]
1284
- end
1285
-
1286
- def split_dn_value(value)
1287
- dn_value = relative_dn_value = nil
1288
- begin
1289
- dn_value = DN.parse(value)
1290
- rescue DistinguishedNameInvalid
1291
- dn_value = DN.parse("#{dn_attribute}=#{value}")
1292
- end
1293
-
1294
- begin
1295
- relative_dn_value = dn_value - DN.parse(base_of_class)
1296
- relative_dn_value = dn_value if relative_dn_value.rdns.empty?
1297
- rescue ArgumentError
1298
- relative_dn_value = dn_value
1299
- end
1300
-
1301
- val, *bases = relative_dn_value.rdns
1302
- [val.values[0], bases.empty? ? nil : DN.new(*bases).to_s]
1303
- end
1304
-
1305
- # define_attribute_methods
1306
- #
1307
- # Make a method entry for _every_ alias of a valid attribute and map it
1308
- # onto the first attribute passed in.
1309
- def define_attribute_methods(attr)
1310
- logger.debug {"stub: called define_attribute_methods(#{attr.inspect})"}
1311
- return if @attr_methods.has_key? attr
1312
- schema.attribute_aliases(attr).each do |ali|
1313
- logger.debug {"associating #{ali} --> #{attr}"}
1314
- @attr_methods[ali] = attr
1315
- logger.debug {"associating #{Inflector.underscore(ali)}" +
1316
- " --> #{attr}"}
1317
- @attr_aliases[Inflector.underscore(ali)] = attr
1318
- logger.debug {"associating #{normalize_attribute_name(ali)}" +
1319
- " --> #{attr}"}
1320
- @normalized_attr_names[normalize_attribute_name(ali)] = attr
1321
- end
1322
- logger.debug {"stub: leaving define_attribute_methods(#{attr.inspect})"}
1323
- end
1324
-
1325
- # array_of
1326
- #
1327
- # Returns the array form of a value, or not an array if
1328
- # false is passed in.
1329
- def array_of(value, to_a=true)
1330
- logger.debug {"stub: called array_of" +
1331
- "(#{value.inspect}, #{to_a.inspect})"}
1332
- case value
1333
- when Array
1334
- if to_a or value.size > 1
1335
- value.collect {|v| array_of(v, to_a)}
1336
- else
1337
- if value.empty?
1338
- nil
1339
- else
1340
- array_of(value.first, to_a)
1341
- end
1342
- end
1343
- when Hash
1344
- if to_a
1345
- [value]
1346
- else
1347
- result = {}
1348
- value.each {|k, v| result[k] = array_of(v, to_a)}
1349
- result
1350
- end
1351
- else
1352
- to_a ? [value.to_s] : value.to_s
1353
- end
1354
- end
1355
-
1356
- def normalize_data(data, except=[])
1357
- result = {}
1358
- data.each do |key, values|
1359
- next if except.include?(key)
1360
- real_name = to_real_attribute_name(key)
1361
- next if real_name and except.include?(real_name)
1362
- real_name ||= key
1363
- result[real_name] ||= []
1364
- result[real_name].concat(values)
1365
- end
1366
- result
1367
- end
1368
-
1369
- def collect_modified_entries(ldap_data, data)
1370
- entries = []
1371
- # Now that all the subtypes will be treated as unique attributes
1372
- # we can see what's changed and add anything that is brand-spankin'
1373
- # new.
1374
- logger.debug {'#collect_modified_entries: traversing ldap_data ' +
1375
- 'determining replaces and deletes'}
1376
- ldap_data.each do |k, v|
1377
- value = data[k] || []
1378
-
1379
- next if v == value
1380
-
1381
- # Create mod entries
1382
- if value.empty?
1383
- # Since some types do not have equality matching rules,
1384
- # delete doesn't work
1385
- # Replacing with nothing is equivalent.
1386
- logger.debug {"#save: removing attribute from existing entry: #{k}"}
1387
- if !data.has_key?(k) and schema.binary_required?(k)
1388
- value = [{'binary' => []}]
1389
- end
1390
- else
1391
- # Ditched delete then replace because attribs with no equality
1392
- # match rules will fails
1393
- logger.debug {"#collect_modified_entries: updating attribute of" +
1394
- " existing entry: #{k}: #{value.inspect}"}
1395
- end
1396
- entries.push([:replace, k, value])
1397
- end
1398
- logger.debug {'#collect_modified_entries: finished traversing' +
1399
- ' ldap_data'}
1400
- logger.debug {'#collect_modified_entries: traversing data ' +
1401
- 'determining adds'}
1402
- data.each do |k, v|
1403
- value = v || []
1404
- next if ldap_data.has_key?(k) or value.empty?
1405
-
1406
- # Detect subtypes and account for them
1407
- logger.debug {"#save: adding attribute to existing entry: " +
1408
- "#{k}: #{value.inspect}"}
1409
- # REPLACE will function like ADD, but doesn't hit EQUALITY problems
1410
- # TODO: Added equality(attr) to Schema
1411
- entries.push([:replace, k, value])
1412
- end
1413
-
1414
- entries
1415
- end
1416
-
1417
- def collect_all_entries(data)
1418
- dn_attr = to_real_attribute_name(dn_attribute)
1419
- dn_value = data[dn_attr]
1420
- logger.debug {'#collect_all_entries: adding all attribute value pairs'}
1421
- logger.debug {"#collect_all_entries: adding " +
1422
- "#{dn_attr.inspect} = #{dn_value.inspect}"}
1423
-
1424
- entries = []
1425
- entries.push([:add, dn_attr, dn_value])
1426
-
1427
- oc_value = data['objectClass']
1428
- logger.debug {"#collect_all_entries: adding objectClass = " +
1429
- "#{oc_value.inspect}"}
1430
- entries.push([:add, 'objectClass', oc_value])
1431
- data.each do |key, value|
1432
- next if value.empty? or key == 'objectClass' or key == dn_attr
1433
-
1434
- logger.debug {"#collect_all_entries: adding attribute to new " +
1435
- "entry: #{key.inspect}: #{value.inspect}"}
1436
- entries.push([:add, key, value])
1437
- end
1438
-
1439
- entries
1440
- end
1441
-
1442
- def check_configuration
1443
- unless dn_attribute
1444
- raise ConfigurationError,
1445
- "dn_attribute not set for this class: #{self.class}"
1446
- end
1447
- end
1448
-
1449
- def create_or_update
1450
- new_entry? ? create : update
1451
- end
1452
-
1453
- def prepare_data_for_saving
1454
- logger.debug {"stub: save called"}
1455
-
1456
- # Expand subtypes to real ldap_data entries
1457
- # We can't reuse @ldap_data because an exception would leave
1458
- # an object in an unknown state
1459
- logger.debug {"#save: expanding subtypes in @ldap_data"}
1460
- ldap_data = normalize_data(@ldap_data)
1461
- logger.debug {'#save: subtypes expanded for @ldap_data'}
1462
-
1463
- # Expand subtypes to real data entries, but leave @data alone
1464
- logger.debug {'#save: expanding subtypes for @data'}
1465
- bad_attrs = @data.keys - attribute_names
1466
- data = normalize_data(@data, bad_attrs)
1467
- logger.debug {'#save: subtypes expanded for @data'}
1468
-
1469
- success = yield(data, ldap_data)
1470
-
1471
- if success
1472
- logger.debug {"#save: resetting @ldap_data to a dup of @data"}
1473
- @ldap_data = Marshal.load(Marshal.dump(data))
1474
- # Delete items disallowed by objectclasses.
1475
- # They should have been removed from ldap.
1476
- logger.debug {'#save: removing attributes from @ldap_data not ' +
1477
- 'sent in data'}
1478
- bad_attrs.each do |remove_me|
1479
- @ldap_data.delete(remove_me)
1480
- end
1481
- logger.debug {'#save: @ldap_data reset complete'}
1482
- end
1483
-
1484
- logger.debug {'stub: save exited'}
1485
- success
1486
- end
1487
-
1488
- def create
1489
- prepare_data_for_saving do |data, ldap_data|
1490
- entries = collect_all_entries(data)
1491
- logger.debug {"#create: adding #{dn}"}
1492
- self.class.add(dn, entries)
1493
- logger.debug {"#create: add successful"}
1494
- @new_entry = false
1495
- true
1496
- end
1497
- end
1498
-
1499
- def update
1500
- prepare_data_for_saving do |data, ldap_data|
1501
- entries = collect_modified_entries(ldap_data, data)
1502
- logger.debug {'#update: traversing data complete'}
1503
- logger.debug {"#update: modifying #{dn}"}
1504
- self.class.modify(dn, entries)
1505
- logger.debug {'#update: modify successful'}
1506
- true
1507
- end
1508
- end
1509
- end # Base
1510
- end # ActiveLdap