ruby-activeldap 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
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