ruby-activeldap 0.7.4 → 0.8.0

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 (74) hide show
  1. data/CHANGES +375 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +58 -0
  4. data/Manifest.txt +33 -0
  5. data/README +63 -0
  6. data/Rakefile +37 -0
  7. data/TODO +31 -0
  8. data/benchmark/bench-al.rb +152 -0
  9. data/lib/{activeldap.rb → active_ldap.rb} +280 -263
  10. data/lib/active_ldap/adaptor/base.rb +29 -0
  11. data/lib/active_ldap/adaptor/ldap.rb +466 -0
  12. data/lib/active_ldap/association/belongs_to.rb +38 -0
  13. data/lib/active_ldap/association/belongs_to_many.rb +40 -0
  14. data/lib/active_ldap/association/collection.rb +80 -0
  15. data/lib/active_ldap/association/has_many.rb +48 -0
  16. data/lib/active_ldap/association/has_many_wrap.rb +56 -0
  17. data/lib/active_ldap/association/proxy.rb +89 -0
  18. data/lib/active_ldap/associations.rb +162 -0
  19. data/lib/active_ldap/attributes.rb +199 -0
  20. data/lib/active_ldap/base.rb +1343 -0
  21. data/lib/active_ldap/callbacks.rb +19 -0
  22. data/lib/active_ldap/command.rb +46 -0
  23. data/lib/active_ldap/configuration.rb +96 -0
  24. data/lib/active_ldap/connection.rb +137 -0
  25. data/lib/{activeldap → active_ldap}/ldap.rb +1 -1
  26. data/lib/active_ldap/object_class.rb +70 -0
  27. data/lib/active_ldap/schema.rb +258 -0
  28. data/lib/{activeldap → active_ldap}/timeout.rb +0 -0
  29. data/lib/{activeldap → active_ldap}/timeout_stub.rb +0 -0
  30. data/lib/active_ldap/user_password.rb +92 -0
  31. data/lib/active_ldap/validations.rb +78 -0
  32. data/rails/plugin/active_ldap/README +54 -0
  33. data/rails/plugin/active_ldap/init.rb +6 -0
  34. data/test/TODO +2 -0
  35. data/test/al-test-utils.rb +337 -0
  36. data/test/command.rb +62 -0
  37. data/test/config.yaml +8 -0
  38. data/test/config.yaml.sample +6 -0
  39. data/test/run-test.rb +17 -0
  40. data/test/test-unit-ext.rb +2 -0
  41. data/test/test_associations.rb +334 -0
  42. data/test/test_attributes.rb +71 -0
  43. data/test/test_base.rb +345 -0
  44. data/test/test_base_per_instance.rb +32 -0
  45. data/test/test_bind.rb +53 -0
  46. data/test/test_callback.rb +35 -0
  47. data/test/test_connection.rb +38 -0
  48. data/test/test_connection_per_class.rb +50 -0
  49. data/test/test_find.rb +36 -0
  50. data/test/test_groupadd.rb +50 -0
  51. data/test/test_groupdel.rb +46 -0
  52. data/test/test_groupls.rb +107 -0
  53. data/test/test_groupmod.rb +51 -0
  54. data/test/test_lpasswd.rb +75 -0
  55. data/test/test_object_class.rb +32 -0
  56. data/test/test_reflection.rb +173 -0
  57. data/test/test_schema.rb +166 -0
  58. data/test/test_user.rb +209 -0
  59. data/test/test_user_password.rb +93 -0
  60. data/test/test_useradd-binary.rb +59 -0
  61. data/test/test_useradd.rb +55 -0
  62. data/test/test_userdel.rb +48 -0
  63. data/test/test_userls.rb +86 -0
  64. data/test/test_usermod-binary-add-time.rb +62 -0
  65. data/test/test_usermod-binary-add.rb +61 -0
  66. data/test/test_usermod-binary-del.rb +64 -0
  67. data/test/test_usermod-lang-add.rb +57 -0
  68. data/test/test_usermod.rb +56 -0
  69. data/test/test_validation.rb +38 -0
  70. metadata +94 -21
  71. data/lib/activeldap/associations.rb +0 -170
  72. data/lib/activeldap/base.rb +0 -1456
  73. data/lib/activeldap/configuration.rb +0 -59
  74. data/lib/activeldap/schema2.rb +0 -217
@@ -1,1456 +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 'activeldap'
16
- # > true
17
- # irb> user = ActiveLDAP::User.new("drewry")
18
- # > #<ActiveLDAP::User:0x402e...
19
- # irb> user.cn
20
- # > "foo"
21
- # irb> user.commonname
22
- # > "foo"
23
- # irb> user.cn = "Will Drewry"
24
- # > "Will Drewry"
25
- # irb> user.cn
26
- # > "Will Drewry"
27
- # irb> user.validate
28
- # > nil
29
- # irb> user.write
30
- #
31
- #
32
-
33
- require 'ldap'
34
- require 'ldap/schema'
35
- require 'log4r'
36
-
37
- module ActiveLDAP
38
- # OO-interface to LDAP assuming pam/nss_ldap-style organization with Active specifics
39
- # Each subclass does a ldapsearch for the matching entry.
40
- # If no exact match, raise an error.
41
- # If match, change all LDAP attributes in accessor attributes on the object.
42
- # -- these are ACTUALLY populated from schema - see subschema.rb example
43
- # -- @conn.schema2().each{|k,vs| vs.each{|v| print("#{k}: #{v}\n")}}
44
- # -- extract objectClasses from match and populate
45
- # Multiple entries become lists.
46
- # If this isn't read-only then lists become multiple entries, etc.
47
-
48
- # AttributeEmpty
49
- #
50
- # An exception raised when a required attribute is found to be empty
51
- class AttributeEmpty < RuntimeError
52
- end
53
-
54
- # ConfigurationError
55
- #
56
- # An exception raised when there is a problem with Base.connect arguments
57
- class ConfigurationError < RuntimeError
58
- end
59
-
60
- # DeleteError
61
- #
62
- # An exception raised when an ActiveLDAP delete action fails
63
- class DeleteError < RuntimeError
64
- end
65
-
66
- # WriteError
67
- #
68
- # An exception raised when an ActiveLDAP write action fails
69
- class WriteError < RuntimeError
70
- end
71
-
72
- # AuthenticationError
73
- #
74
- # An exception raised when user authentication fails
75
- class AuthenticationError < RuntimeError
76
- end
77
-
78
- # ConnectionError
79
- #
80
- # An exception raised when the LDAP conenction fails
81
- class ConnectionError < RuntimeError
82
- end
83
-
84
- # ObjectClassError
85
- #
86
- # An exception raised when an objectClass is not defined in the schema
87
- class ObjectClassError < RuntimeError
88
- end
89
-
90
- # AttributeAssignmentError
91
- #
92
- # An exception raised when there is an issue assigning a value to
93
- # an attribute
94
- class AttributeAssignmentError < RuntimeError
95
- end
96
-
97
- # TimeoutError
98
- #
99
- # An exception raised when a connection action fails due to a timeout
100
- class TimeoutError < RuntimeError
101
- end
102
-
103
- # Base
104
- #
105
- # Base is the primary class which contains all of the core
106
- # ActiveLDAP functionality. It is meant to only ever be subclassed
107
- # by extension classes.
108
- class Base
109
- # Parsed schema structures
110
- attr_reader :must, :may
111
- attr_accessor :logger
112
-
113
- # All class-wide variables
114
- @@config = nil # Container for current connection settings
115
- @@schema = nil # LDAP server's schema
116
- @@conn = nil # LDAP connection
117
- @@reconnect_attempts = 0 # Number of reconnects attempted
118
-
119
- # Driver generator
120
- #
121
- # TODO add type checking
122
- # This let's you call this method to create top-level extension object. This
123
- # is really just a proof of concept and has not truly useful purpose.
124
- # example: Base.create_object(:class => "user", :dnattr => "uid", :classes => ['top'])
125
- #
126
- # THIS METHOD IS DANGEROUS. INPUT IS NOT SANITIZED.
127
- def Base.create_object(config={})
128
- # Just upcase the first letter of the new class name
129
- str = config[:class]
130
- class_name = str[0].chr.upcase + str[1..-1]
131
-
132
- attr = config[:dnattr] # "uid"
133
- prefix = config[:base] # "ou=People"
134
- # [ 'top', 'posixAccount' ]
135
- classes_array = config[:classes] || []
136
- # [ [ :groups, {:class_name => "Group", :foreign_key => "memberUid"}] ]
137
- belongs_to_array = config[:belongs_to] || []
138
- # [ [ :members, {:class_name => "User", :foreign_key => "uid", :local_key => "memberUid"}] ]
139
- has_many_array = config[:has_many] || []
140
-
141
- raise TypeError, ":objectclasses must be an array" unless classes_array.respond_to? :size
142
- raise TypeError, ":belongs_to must be an array" unless belongs_to_array.respond_to? :size
143
- raise TypeError, ":has_many must be an array" unless has_many_array.respond_to? :size
144
-
145
- # Build classes array
146
- classes = '['
147
- classes_array.map! {|x| x = "'#{x}'"}
148
- classes << classes_array.join(', ')
149
- classes << ']'
150
-
151
- # Build belongs_to
152
- belongs_to = []
153
- if belongs_to_array.size > 0
154
- belongs_to_array.each do |bt|
155
- line = [ "belongs_to :#{bt[0]}" ]
156
- bt[1].keys.each do |key|
157
- line << ":#{key} => '#{bt[1][key]}'"
158
- end
159
- belongs_to << line.join(', ')
160
- end
161
- end
162
-
163
- # Build has_many
164
- has_many = []
165
- if has_many_array.size > 0
166
- has_many_array.each do |hm|
167
- line = [ "has_many :#{hm[0]}" ]
168
- hm[1].keys.each do |key|
169
- line << ":#{key} => '#{hm[1][key]}'"
170
- end
171
- has_many << line.join(', ')
172
- end
173
- end
174
-
175
- self.class.module_eval <<-"end_eval"
176
- class ::#{class_name} < ActiveLDAP::Base
177
- ldap_mapping :dnattr => "#{attr}", :prefix => "#{prefix}", :classes => #{classes}
178
- #{belongs_to.join("\n")}
179
- #{has_many.join("\n")}
180
- end
181
- end_eval
182
- end
183
-
184
- # Connect and bind to LDAP creating a class variable for use by all ActiveLDAP
185
- # objects.
186
- #
187
- # == +config+
188
- # +config+ must be a hash that may contain any of the following fields:
189
- # :user, :password_block, :logger, :host, :port, :base, :bind_format, :try_sasl, :allow_anonymous
190
- # :user specifies the username to bind with.
191
- # :bind_format specifies the string to substitute the username into on bind. e.g. uid=%s,ou=People,dc=dataspill,dc=org. Overrides @@bind_format.
192
- # :password_block specifies a Proc object that will yield a String to be used as the password when called.
193
- # :logger specifies a preconfigured Log4r::Logger to be used for all logging
194
- # :host sets the LDAP server hostname
195
- # :port sets the LDAP server port
196
- # :base overwrites Base.base - this affects EVERYTHING
197
- # :try_sasl indicates that a SASL bind should be attempted when binding to the server (default: false)
198
- # :allow_anonymous indicates that a true anonymous bind is allowed when trying to bind to the server (default: true)
199
- # :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite.
200
- # :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
201
- # :method - whether to use :ssl, :tls, or :plain (unencrypted)
202
- # :retry_wait - seconds to wait before retrying a connection
203
- # :ldap_scope - dictates how to find objects. ONELEVEL by default to avoid dn_attr collisions across OUs. Think before changing.
204
- # :return_objects - indicates whether find/find_all will return objects or just the distinguished name attribute value of the matches
205
- # :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned.
206
- # :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true
207
- # See lib/configuration.rb for defaults for each option
208
- def Base.connect(config={})
209
- # Process config
210
- # Class options
211
- ## These will be replace by configuration.rb defaults if defined
212
- @@config = DEFAULT_CONFIG.dup
213
- config.keys.each do |key|
214
- case key
215
- when :base
216
- # Scrub before inserting
217
- base = config[:base].gsub(/['}{#]/, '')
218
- Base.class_eval("def Base.base();'#{base}';end")
219
- when :ldap_scope
220
- if config[:ldap_scope].class != Fixnum
221
- raise ConfigurationError, ':ldap_scope must be a Fixnum'
222
- end
223
- Base.class_eval("def Base.ldap_scope();#{config[:ldap_scope]};end")
224
- else
225
- @@config[key] = config[key]
226
- end
227
- end
228
- # Assign a easier name for the logger
229
- @@logger = @@config[:logger] || nil
230
- # Setup default logger to console
231
- if @@logger.nil?
232
- @@logger = Log4r::Logger.new('activeldap')
233
- @@logger.level = Log4r::OFF
234
- Log4r::StderrOutputter.new 'console'
235
- @@logger.add('console')
236
- end
237
-
238
- # Reset for the new connection
239
- @@reconnect_attempts = 0
240
-
241
- # Make the connection.
242
- do_connect()
243
-
244
- # Make irb users happy with a 'true'
245
- return true
246
- end # Base.connect
247
-
248
- # Base.close
249
- # This method deletes the LDAP connection object.
250
- # This does NOT reset any overridden values from a Base.connect call.
251
- def Base.close
252
- begin
253
- @@conn.unbind unless @@conn.nil?
254
- rescue
255
- # Doesn't matter.
256
- end
257
- @@conn = nil
258
- # Make sure it is cleaned up
259
- # This causes Ruby/LDAP memory corruption.
260
- # ObjectSpace.garbage_collect
261
- end
262
-
263
- # Return the LDAP connection object currently in use
264
- # Alternately execute a command against the connection
265
- # object "safely" using a given block. Use the given
266
- # "errmsg" for any error conditions.
267
- def Base.connection(exc=RuntimeError.new('unknown error'), try_reconnect = true)
268
- # Block was given! Let's safely provide access.
269
- if block_given?
270
- begin
271
- Timeout.alarm(@@config[:timeout]) do
272
- begin
273
- yield @@conn
274
- rescue => e
275
- # Raise an LDAP error instead of RuntimeError or whatever
276
-
277
- raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0
278
- # Else reraise
279
-
280
- raise e
281
- end
282
- end
283
- rescue Timeout::Error => e
284
- @@logger.error('Requested action timed out.')
285
- retry if try_reconnect and @@config[:retry_on_timeout] and Base.reconnect()
286
- message = e.message
287
- message = exc.message unless exc.nil?
288
- @@logger.error(message)
289
- raise TimeoutError, message
290
- rescue LDAP::ServerDown, LDAP::ResultError, RuntimeError => e
291
- @@logger.error("#{e.class} exception occurred in connection block")
292
- @@logger.error("Exception message: #{e.message}")
293
- @@logger.error("Exception backtrace: #{e.backtrace}")
294
- @@logger.error(exc.message) unless exc.nil?
295
- retry if try_reconnect and Base.reconnect()
296
- raise exc unless exc.nil?
297
- return nil
298
- rescue LDAP::UndefinedType => e
299
- @@logger.error("#{e.class} exception occurred in connection block")
300
- @@logger.error("Exception message: #{e.message}")
301
- @@logger.error("Exception backtrace: #{e.backtrace}")
302
- # Do not retry - not a connection error
303
- raise exc unless exc.nil?
304
- return nil
305
- # Catch all - to be remedied later
306
- rescue => e
307
- @@logger.error("#{e.class} exception occurred in connection block")
308
- @@logger.error("Exception message: #{e.message}")
309
- @@logger.error("Exception backtrace: #{e.backtrace}")
310
- @@logger.error("Error in catch all: please send debug log to ActiveLDAP author")
311
- @@logger.error(exc.message) unless exc.nil?
312
- raise exc unless exc.nil?
313
- return nil
314
- end
315
- end
316
- return @@conn
317
- end
318
-
319
- # Set the LDAP connection avoiding Base.connect or multiplexing connections
320
- def Base.connection=(conn)
321
- @@conn = conn
322
- end
323
-
324
- # Determine if we have exceed the retry limit or not.
325
- # True is reconnecting is allowed - False if not.
326
- def Base.can_reconnect?
327
- # Allow connect if we've never connected.
328
- return true unless @@config
329
- if @@reconnect_attempts < (@@config[:retries] - 1) or
330
- @@config[:retries] < 0
331
- return true
332
- end
333
- return false
334
- end
335
-
336
- # Attempts to reconnect up to the number of times allowed
337
- # If forced, try once then fail with ConnectionError if not connected.
338
- def Base.reconnect(force=false)
339
- unless @@config
340
- @@logger.error('Ignoring force: Base.reconnect called before Base.connect') if force
341
-
342
- Base.connect
343
- return true
344
- end
345
- not_connected = true
346
- while not_connected
347
- if Base.can_reconnect?
348
-
349
- Base.close()
350
-
351
- # Reset the attempts if this was forced.
352
- @@reconnect_attempts = 0 if force
353
- @@reconnect_attempts += 1 if @@config[:retries] >= 0
354
- begin
355
- do_connect()
356
- not_connected = false
357
- rescue => detail
358
- @@logger.error("Reconnect to server failed: #{detail.exception}")
359
- @@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}")
360
- # Do not loop if forced
361
- raise ConnectionError, detail.message if force
362
- end
363
- else
364
- # Raise a warning
365
- raise ConnectionError, 'Giving up trying to reconnect to LDAP server.'
366
- end
367
-
368
- # Sleep before looping
369
- sleep @@config[:retry_wait]
370
- end
371
- return true
372
- end
373
-
374
- # Return the schema object
375
- def Base.schema
376
- @@schema
377
- end
378
-
379
- # search
380
- #
381
- # Wraps Ruby/LDAP connection.search to make it easier to search for specific
382
- # data without cracking open Base.connection
383
- def Base.search(config={})
384
- Base.reconnect if Base.connection.nil? and Base.can_reconnect?
385
-
386
- config[:filter] = 'objectClass=*' unless config.has_key? :filter
387
- config[:attrs] = [] unless config.has_key? :attrs
388
- config[:scope] = LDAP::LDAP_SCOPE_SUBTREE unless config.has_key? :scope
389
- config[:base] = base() unless config.has_key? :base
390
-
391
- values = []
392
- config[:attrs] = config[:attrs].to_a # just in case
393
-
394
- result = Base.connection() do |conn|
395
- conn.search(config[:base], config[:scope], config[:filter], config[:attrs]) do |m|
396
- res = {}
397
- res['dn'] = [m.dn.dup] # For consistency with the below
398
- m.attrs.each do |attr|
399
- if config[:attrs].member? attr or config[:attrs].empty?
400
- res[attr] = m.vals(attr).dup
401
- end
402
- end
403
- values.push(res)
404
- end
405
- end
406
- if result.nil?
407
- # Do nothing on failure
408
-
409
- end
410
- return values
411
- end
412
-
413
- # find
414
- #
415
- # Finds the first match for value where |value| is the value of some
416
- # |field|, or the wildcard match. This is only useful for derived classes.
417
- # usage: Subclass.find(:attribute => "cn", :value => "some*val", :objects => true)
418
- # Subclass.find('some*val')
419
- #
420
- def Base.find(config='*')
421
- Base.reconnect if Base.connection.nil? and Base.can_reconnect?
422
-
423
- if self.class == Class
424
- klass = self.ancestors[0].to_s.split(':').last
425
- real_klass = self.ancestors[0]
426
- else
427
- klass = self.class.to_s.split(':').last
428
- real_klass = self.class
429
- end
430
-
431
- # Allow a single string argument
432
- attr = dnattr()
433
- objects = @@config[:return_objects]
434
- val = config
435
- # Or a hash
436
- if config.respond_to?(:has_key?)
437
- attr = config[:attribute] || dnattr()
438
- val = config[:value] || '*'
439
- objects = config[:objects] unless config[:objects].nil?
440
- end
441
-
442
- Base.connection(ConnectionError.new("Failed in #{self.class}#find(#{config.inspect})")) do |conn|
443
- # Get some attributes
444
- conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
445
- # Extract the dnattr value
446
- dnval = m.dn.split(/,/)[0].split(/=/)[1]
447
-
448
- if objects
449
- return real_klass.new(m)
450
- else
451
- return dnval
452
- end
453
- end
454
- end
455
- # If we're here, there were no results
456
- return nil
457
- end
458
- private_class_method :find
459
-
460
- # find_all
461
- #
462
- # Finds all matches for value where |value| is the value of some
463
- # |field|, or the wildcard match. This is only useful for derived classes.
464
- def Base.find_all(config='*')
465
- Base.reconnect if Base.connection.nil? and Base.can_reconnect?
466
-
467
- if self.class == Class
468
- real_klass = self.ancestors[0]
469
- else
470
- real_klass = self.class
471
- end
472
-
473
- # Allow a single string argument
474
- val = config
475
- attr = dnattr()
476
- objects = @@config[:return_objects]
477
- # Or a hash
478
- if config.respond_to?(:has_key?)
479
- val = config[:value] || '*'
480
- attr = config[:attribute] || dnattr()
481
- objects = config[:objects] unless config[:objects].nil?
482
- end
483
-
484
- matches = []
485
- Base.connection(ConnectionError.new("Failed in #{self.class}#find_all(#{config.inspect})")) do |conn|
486
- # Get some attributes
487
- conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
488
- # Extract the dnattr value
489
- dnval = m.dn.split(/,/)[0].split(/=/)[1]
490
-
491
- if objects
492
- matches.push(real_klass.new(m))
493
- else
494
- matches.push(dnval)
495
- end
496
- end
497
- end
498
- return matches
499
- end
500
- private_class_method :find_all
501
-
502
- # Base.base
503
- #
504
- # This method when included into Base provides
505
- # an inheritable, overwritable configuration setting
506
- #
507
- # This should be a string with the base of the
508
- # ldap server such as 'dc=example,dc=com', and
509
- # it should be overwritten by including
510
- # configuration.rb into this class.
511
- # When subclassing, the specified prefix will be concatenated.
512
- def Base.base
513
- 'dc=localdomain'
514
- end
515
-
516
- # Base.dnattr
517
- #
518
- # This is a placeholder for the class method that will
519
- # be overridden on calling ldap_mapping in a subclass.
520
- # Using a class method allows for clean inheritance from
521
- # classes that already have a ldap_mapping.
522
- def Base.dnattr
523
- ''
524
- end
525
-
526
- # Base.required_classes
527
- #
528
- # This method when included into Base provides
529
- # an inheritable, overwritable configuration setting
530
- #
531
- # The value should be the minimum required objectClasses
532
- # to make an object in the LDAP server, or an empty array [].
533
- # This should be overwritten by configuration.rb.
534
- # Note that subclassing does not cause concatenation of
535
- # arrays to occurs.
536
- def Base.required_classes
537
- []
538
- end
539
-
540
- # Base.ldap_scope
541
- #
542
- # This method when included into Base provides
543
- # an inheritable, overwritable configuration setting
544
- #
545
- # This value should be the default LDAP scope behavior
546
- # desired.
547
- def Base.ldap_scope
548
- LDAP::LDAP_SCOPE_ONELEVEL
549
- end
550
-
551
-
552
-
553
- ### All instance methods, etc
554
-
555
- # new
556
- #
557
- # Creates a new instance of Base initializing all class and all
558
- # initialization. Defines local defaults. See examples If multiple values
559
- # exist for dnattr, the first one put here will be authoritative
560
- # TODO: Add # support for relative distinguished names
561
- # val can be a dn attribute value, a full DN, or a LDAP::Entry. The use
562
- # with a LDAP::Entry is primarily meant for internal use by find and
563
- # find_all.
564
- #
565
- def initialize(val)
566
- @exists = false
567
- # Make sure we're connected
568
- Base.reconnect if Base.connection.nil? and Base.can_reconnect?
569
-
570
- if val.class == LDAP::Entry
571
- # Call import, which is basically initialize
572
- # without accessing LDAP.
573
-
574
- import(val)
575
- return
576
- end
577
- if val.class != String
578
- raise TypeError, "Object key must be a String"
579
- end
580
-
581
- @data = {} # where the r/w entry data is stored
582
- @ldap_data = {} # original ldap entry data
583
- @attr_methods = {} # list of valid method calls for attributes used for dereferencing
584
- @last_oc = false # for use in other methods for "caching"
585
- if dnattr().empty?
586
- raise ConfigurationError, "dnattr() not set for this class: #{self.class}"
587
- end
588
-
589
- # Extract dnattr if val looks like a dn
590
- if val.match(/^#{dnattr()}=([^,=]+),/i)
591
- val = $1
592
- elsif val.match(/[=,]/)
593
- @@logger.info "initialize: Changing val from '#{val}' to '' because it doesn't match the DN."
594
- val = ''
595
- end
596
-
597
- # Do a search - if it exists, pull all data and parse schema, if not, just set the hierarchical data
598
- if val.class != String or val.empty?
599
- raise TypeError, 'a dn attribute String must be supplied ' +
600
- 'on initialization'
601
- else
602
- # Create what should be the authoritative DN
603
- @dn = "#{dnattr()}=#{val},#{base()}"
604
-
605
- # Search for the existing entry
606
- Base.connection(ConnectionError.new("Failed in #{self.class}#new(#{val.inspect})")) do |conn|
607
- # Get some attributes
608
- conn.search(base(), ldap_scope(), "(#{dnattr()}=#{val})") do |m|
609
- @exists = true
610
- # Save DN
611
- @dn = m.dn
612
- # Load up data into tmp
613
-
614
- m.attrs.each do |attr|
615
- # Load with subtypes just like @data
616
-
617
- safe_attr, value = make_subtypes(attr, m.vals(attr).dup)
618
-
619
- # Add subtype to any existing values
620
- if @ldap_data.has_key? safe_attr
621
- value.each do |v|
622
- @ldap_data[safe_attr].push(v)
623
- end
624
- else
625
- @ldap_data[safe_attr] = value
626
- end
627
- end
628
- end
629
- end
630
- end
631
-
632
- # Do the actual object setup work.
633
- if @exists
634
- # Make sure the server uses objectClass and not objectclass
635
- unless @ldap_data.has_key?('objectClass')
636
- real_objc = @ldap_data.grep(/^objectclass$/i)
637
- if real_objc.size == 1
638
- @ldap_data['objectClass'] = @ldap_data[real_objc]
639
- @ldap_data.delete(real_objc)
640
- else
641
- raise AttributeEmpty, 'objectClass was not sent by LDAP server!'
642
- end
643
- end
644
-
645
- # Populate schema data
646
- send(:apply_objectclass, @ldap_data['objectClass'])
647
-
648
- # Populate real data now that we have the schema with aliases
649
- @ldap_data.each do |pair|
650
- real_attr = @attr_methods[pair[0]]
651
-
652
- if real_attr.nil?
653
- @@logger.error("Unable to resolve attribute value #{pair[0].inspect}. " +
654
- "Unpredictable behavior likely!")
655
- end
656
- @data[real_attr] = pair[1].dup
657
-
658
- end
659
- else
660
- send(:apply_objectclass, required_classes())
661
-
662
- # Setup dn attribute (later rdn too!)
663
- real_dnattr = @attr_methods[dnattr()]
664
- @data[real_dnattr] = val
665
-
666
- end
667
- end # initialize
668
-
669
- # Hide new in Base
670
- private_class_method :new
671
-
672
- # attributes
673
- #
674
- # Return attribute methods so that a program can determine available
675
- # attributes dynamically without schema awareness
676
- def attributes
677
-
678
- send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc
679
- return @attr_methods.keys.map {|x|x.downcase}.uniq
680
- end
681
-
682
- # exists?
683
- #
684
- # Return whether the entry exists in LDAP or not
685
- def exists?
686
-
687
- return @exists
688
- end
689
-
690
- # dn
691
- #
692
- # Return the authoritative dn
693
- def dn
694
-
695
- return @dn.dup
696
- end
697
-
698
- # validate
699
- #
700
- # Basic validation:
701
- # - Verify that every 'MUST' specified in the schema has a value defined
702
- # - Enforcement of undefined attributes is handled in the objectClass= method
703
- # Must call enforce_types() first before enforcement can be guaranteed
704
- def validate
705
-
706
- # Clean up attr values, etc
707
- send(:enforce_types)
708
-
709
- # Validate objectclass settings
710
- @data['objectClass'].each do |klass|
711
- unless klass.class == String
712
- raise TypeError, "Value in objectClass array is not a String. (#{klass.class}:#{klass.inspect})"
713
- end
714
- unless Base.schema.names("objectClasses").member? klass
715
- raise ObjectClassError, "objectClass '#{klass}' unknown to LDAP server."
716
- end
717
- end
718
-
719
- # make sure this doesn't drop any of the required objectclasses
720
- required_classes().each do |oc|
721
- unless @data['objectClass'].member? oc.to_s
722
- raise ObjectClassError, "'#{oc}' must be a defined objectClass for class '#{self.class}' as set in the ldap_mapping"
723
- end
724
- end
725
-
726
- # Make sure all MUST attributes have a value
727
- @data['objectClass'].each do |objc|
728
- @must.each do |req_attr|
729
- # Downcase to ensure we catch schema problems
730
- deref = @attr_methods[req_attr.downcase]
731
- # Set default if it wasn't yet set.
732
- @data[deref] = [] if @data[deref].nil?
733
- # Check for missing requirements.
734
- if @data[deref].empty?
735
- raise AttributeEmpty,
736
- "objectClass '#{objc}' requires attribute '#{Base.schema.attribute_aliases(req_attr).join(', ')}'"
737
- end
738
- end
739
- end
740
-
741
- end
742
-
743
- # delete
744
- #
745
- # Delete this entry from LDAP
746
- def delete
747
-
748
- Base.connection(DeleteError.new(
749
- "Failed to delete LDAP entry: '#{@dn}'")) do |conn|
750
- conn.delete(@dn)
751
- @exists = false
752
- end
753
- end
754
-
755
-
756
- # write
757
- #
758
- # Write and validate this object into LDAP
759
- # either adding or replacing attributes
760
- # TODO: Binary data support
761
- # TODO: Relative DN support
762
- def write
763
-
764
- # Validate against the objectClass requirements
765
- validate
766
-
767
- # Put all changes into one change entry to ensure
768
- # automatic rollback upon failure.
769
- entry = []
770
-
771
-
772
- # Expand subtypes to real ldap_data entries
773
- # We can't reuse @ldap_data because an exception would leave
774
- # an object in an unknown state
775
-
776
- ldap_data = Marshal.load(Marshal.dump(@ldap_data))
777
-
778
-
779
- ldap_data.keys.each do |key|
780
- ldap_data[key].each do |value|
781
- if value.class == Hash
782
- suffix, real_value = extract_subtypes(value)
783
- if ldap_data.has_key? key + suffix
784
- ldap_data[key + suffix].push(real_value)
785
- else
786
- ldap_data[key + suffix] = real_value
787
- end
788
- ldap_data[key].delete(value)
789
- end
790
- end
791
- end
792
-
793
-
794
- # Expand subtypes to real data entries, but leave @data alone
795
-
796
- data = Marshal.load(Marshal.dump(@data))
797
-
798
-
799
-
800
- bad_attrs = @data.keys - (@must+@may)
801
- bad_attrs.each do |removeme|
802
- data.delete(removeme)
803
- end
804
-
805
-
806
-
807
-
808
- data.keys.each do |key|
809
- data[key].each do |value|
810
- if value.class == Hash
811
- suffix, real_value = extract_subtypes(value)
812
- if data.has_key? key + suffix
813
- data[key + suffix].push(real_value)
814
- else
815
- data[key + suffix] = real_value
816
- end
817
- data[key].delete(value)
818
- end
819
- end
820
- end
821
-
822
-
823
- if @exists
824
- # Cycle through all attrs to determine action
825
- action = {}
826
-
827
- replaceable = []
828
- # Now that all the subtypes will be treated as unique attributes
829
- # we can see what's changed and add anything that is brand-spankin'
830
- # new.
831
-
832
- ldap_data.each do |pair|
833
- suffix = ''
834
- binary = 0
835
-
836
- name, *suffix_a = pair[0].split(/;/)
837
- suffix = ';'+ suffix_a.join(';') if suffix_a.size > 0
838
- name = @attr_methods[name]
839
- name = pair[0].split(/;/)[0] if name.nil? # for objectClass, or removed vals
840
- value = data[name+suffix]
841
- # If it doesn't exist, don't freak out.
842
- value = [] if value.nil?
843
-
844
- # Detect subtypes and account for them
845
- binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name
846
-
847
- replaceable.push(name+suffix)
848
- if pair[1] != value
849
- # Create mod entries
850
- if not value.empty?
851
- # Ditched delete then replace because attribs with no equality match rules
852
- # will fails
853
-
854
- entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value))
855
- else
856
- # Since some types do not have equality matching rules, delete doesn't work
857
- # Replacing with nothing is equivalent.
858
-
859
- entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, []))
860
- end
861
- end
862
- end
863
-
864
-
865
- data.each do |pair|
866
- suffix = ''
867
- binary = 0
868
-
869
- name, *suffix_a = pair[0].split(/;/)
870
- suffix = ';' + suffix_a.join(';') if suffix_a.size > 0
871
- name = @attr_methods[name]
872
- name = pair[0].split(/;/)[0] if name.nil? # for obj class or removed vals
873
- value = pair[1]
874
- # Make sure to change this to an Array if there was mistake earlier.
875
- value = [] if value.nil?
876
-
877
- if not replaceable.member? name+suffix
878
- # Detect subtypes and account for them
879
- binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name
880
-
881
- # REPLACE will function like ADD, but doesn't hit EQUALITY problems
882
- # TODO: Added equality(attr) to Schema2
883
- entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value)) unless value.empty?
884
- end
885
- end
886
-
887
- Base.connection(WriteError.new(
888
- "Failed to modify: '#{entry}'")) do |conn|
889
-
890
- conn.modify(@dn, entry)
891
-
892
- end
893
- else # add everything!
894
-
895
-
896
- entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, @attr_methods[dnattr()],
897
- data[@attr_methods[dnattr()]]))
898
-
899
- entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, 'objectClass',
900
- data[@attr_methods['objectClass']]))
901
- data.each do |pair|
902
- if pair[1].size > 0 and pair[0] != 'objectClass' and pair[0] != @attr_methods[dnattr()]
903
- # Detect subtypes and account for them
904
- if Base.schema.binary? pair[0].split(/;/)[0]
905
- binary = LDAP::LDAP_MOD_BVALUES
906
- else
907
- binary = 0
908
- end
909
-
910
- entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD|binary, pair[0], pair[1]))
911
- end
912
- end
913
- Base.connection(WriteError.new(
914
- "Failed to add: '#{entry}'")) do |conn|
915
-
916
- conn.add(@dn, entry)
917
-
918
- @exists = true
919
- end
920
- end
921
-
922
- @ldap_data = Marshal.load(Marshal.dump(data))
923
- # Delete items disallowed by objectclasses.
924
- # They should have been removed from ldap.
925
-
926
- bad_attrs.each do |removeme|
927
- @ldap_data.delete(removeme)
928
- end
929
-
930
-
931
- end
932
-
933
- # method_missing
934
- #
935
- # If a given method matches an attribute or an attribute alias
936
- # then call the appropriate method.
937
- # TODO: Determine if it would be better to define each allowed method
938
- # using class_eval instead of using method_missing. This would
939
- # give tab completion in irb.
940
- def method_missing(name, *args)
941
-
942
-
943
- # dynamically update the available attributes without requiring an
944
- # explicit call. The cache 'last_oc' saves a lot of cpu time.
945
- if @data['objectClass'] != @last_oc
946
-
947
- send(:apply_objectclass, @data['objectClass'])
948
- end
949
- key = name.to_s
950
- case key
951
- when /^(\S+)=$/
952
- real_key = $1
953
-
954
- if @attr_methods.has_key? real_key
955
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size != 1
956
-
957
- return send(:attribute_method=, real_key, args[0])
958
- end
959
- else
960
-
961
- if @attr_methods.has_key? key
962
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size > 1
963
- return attribute_method(key, *args)
964
- end
965
- end
966
- raise NoMethodError, "undefined method `#{key}' for #{self}"
967
- end
968
-
969
- # Add available attributes to the methods
970
- alias_method :__methods, :methods
971
- def methods
972
- return __methods + attributes()
973
- end
974
-
975
-
976
- private
977
-
978
- # import(LDAP::Entry)
979
- #
980
- # Overwrites an existing entry (usually called by new)
981
- # with the data given in the data given in LDAP::Entry.
982
- #
983
- def import(entry=nil)
984
-
985
- if entry.class != LDAP::Entry
986
- raise TypeError, "argument must be a LDAP::Entry"
987
- end
988
-
989
- @data = {} # where the r/w entry data is stored
990
- @ldap_data = {} # original ldap entry data
991
- @attr_methods = {} # list of valid method calls for attributes used for dereferencing
992
-
993
- # Get some attributes
994
- @dn = entry.dn
995
- entry.attrs.each do |attr|
996
- # Load with subtypes just like @data
997
-
998
- safe_attr, value = make_subtypes(attr, entry.vals(attr).dup)
999
-
1000
- # Add subtype to any existing values
1001
- if @ldap_data.has_key? safe_attr
1002
- value.each do |v|
1003
- @ldap_data[safe_attr].push(v)
1004
- end
1005
- else
1006
- @ldap_data[safe_attr] = value
1007
- end
1008
- end
1009
- # Assume if we are importing it that it exists
1010
- @exists = true
1011
- # Populate schema data
1012
- send(:apply_objectclass, @ldap_data['objectClass'])
1013
-
1014
- # Populate real data now that we have the schema with aliases
1015
- @ldap_data.each do |pair|
1016
- real_attr = @attr_methods[pair[0]]
1017
-
1018
- @data[real_attr] = pair[1].dup
1019
-
1020
- end
1021
- end # import
1022
-
1023
- # enforce_types
1024
- #
1025
- # enforce_types applies your changes without attempting to write to LDAP. This means that
1026
- # if you set userCertificate to somebinary value, it will wrap it up correctly.
1027
- def enforce_types
1028
-
1029
- send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc
1030
- # Enforce attribute value formatting
1031
- @data.keys.each do |key|
1032
- @data[key] = attribute_input_handler(key, @data[key])
1033
- end
1034
-
1035
- return true
1036
- end
1037
-
1038
- # apply_objectclass
1039
- #
1040
- # objectClass= special case for updating appropriately
1041
- # This updates the objectClass entry in @data. It also
1042
- # updating all required and allowed attributes while
1043
- # removing defined attributes that are no longer valid
1044
- # given the new objectclasses.
1045
- def apply_objectclass(val)
1046
-
1047
- new_oc = val
1048
- new_oc = [val] if new_oc.class != Array
1049
- if defined?(@last_oc).nil?
1050
- @last_oc = false
1051
- end
1052
- return new_oc if @last_oc == new_oc
1053
-
1054
- # Store for caching purposes
1055
- @last_oc = new_oc.dup
1056
-
1057
- # Set the actual objectClass data
1058
- define_attribute_methods('objectClass')
1059
- @data['objectClass'] = new_oc.uniq
1060
-
1061
- # Build |data| from schema
1062
- # clear attr_method mapping first
1063
- @attr_methods = {}
1064
- @must = []
1065
- @may = []
1066
- new_oc.each do |objc|
1067
- # get all attributes for the class
1068
- attributes = Base.schema.class_attributes(objc.to_s)
1069
- @must += attributes[:must]
1070
- @may += attributes[:may]
1071
- end
1072
- @must.uniq!
1073
- @may.uniq!
1074
- (@must+@may).each do |attr|
1075
- # Update attr_method with appropriate
1076
- define_attribute_methods(attr)
1077
- end
1078
- end
1079
-
1080
-
1081
-
1082
- # Enforce typing:
1083
- # Hashes are for subtypes
1084
- # Arrays are for multiple entries
1085
- def attribute_input_handler(attr, value)
1086
-
1087
- if attr.nil?
1088
- raise RuntimeError, 'The first argument, attr, must not be nil. Please report this as a bug!'
1089
- end
1090
- binary = Base.schema.binary_required? attr
1091
- single = Base.schema.single_value? attr
1092
- case value.class.to_s
1093
- when 'Array'
1094
- if single and value.size > 1
1095
- raise TypeError, "Attribute #{attr} can only have a single value"
1096
- end
1097
- value.map! do |entry|
1098
- if entry.class != Hash
1099
-
1100
- entry = entry.to_s
1101
- end
1102
- entry = attribute_input_handler(attr, entry)[0]
1103
- end
1104
- when 'Hash'
1105
- if value.keys.size > 1
1106
- raise TypeError, "Hashes must have one key-value pair only."
1107
- end
1108
- unless value.keys[0].match(/^(lang-[a-z][a-z]*)|(binary)$/)
1109
- @@logger.warn("unknown subtype did not match lang-* or binary: #{value.keys[0]}")
1110
- end
1111
- # Contents MUST be a String or an Array
1112
- if value.keys[0] != 'binary' and binary
1113
- suffix, real_value = extract_subtypes(value)
1114
- value = make_subtypes(name + suffix + ';binary', real_value)
1115
- end
1116
- value = [value]
1117
- when 'String'
1118
- if binary
1119
- value = {'binary' => value}
1120
- end
1121
- return [value]
1122
- else
1123
- value = [value.to_s]
1124
- end
1125
- return value
1126
- end
1127
-
1128
- # make_subtypes
1129
- #
1130
- # Makes the Hashized value from the full attributename
1131
- # e.g. userCertificate;binary => "some_bin"
1132
- # becomes userCertificate => {"binary" => "some_bin"}
1133
- def make_subtypes(attr, value)
1134
-
1135
- return [attr, value] unless attr.match(/;/)
1136
-
1137
- ret_attr, *subtypes = attr.split(/;/)
1138
- return [ret_attr, [make_subtypes_helper(subtypes, value)]]
1139
- end
1140
-
1141
- # make_subtypes_helper
1142
- #
1143
- # This is a recursive function for building
1144
- # nested hashed from multi-subtyped values
1145
- def make_subtypes_helper(subtypes, value)
1146
-
1147
- return value if subtypes.size == 0
1148
- return {subtypes[0] => make_subtypes_helper(subtypes[1..-1], value)}
1149
- end
1150
-
1151
- # extract_subtypes
1152
- #
1153
- # Extracts all of the subtypes from a given set of nested hashes
1154
- # and returns the attribute suffix and the final true value
1155
- def extract_subtypes(value)
1156
-
1157
- subtype = ''
1158
- ret_val = value
1159
- if value.class == Hash
1160
- subtype = ';' + value.keys[0]
1161
- ret_val = value[value.keys[0]]
1162
- subsubtype = ''
1163
- if ret_val.class == Hash
1164
- subsubtype, ret_val = extract_subtypes(ret_val)
1165
- end
1166
- subtype += subsubtype
1167
- end
1168
- ret_val = [ret_val] unless ret_val.class == Array
1169
- return subtype, ret_val
1170
- end
1171
-
1172
-
1173
- # Performs the actually connection. This separate so that it may
1174
- # be called to refresh stale connections.
1175
- def Base.do_connect()
1176
- begin
1177
- case @@config[:method]
1178
- when :ssl
1179
- @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], false)
1180
- when :tls
1181
- @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], true)
1182
- when :plain
1183
- @@conn = LDAP::Conn.new(@@config[:host], @@config[:port])
1184
- else
1185
- raise ConfigurationError,"#{@@config[:method]} is not one of the available connect methods :ssl, :tls, or :plain"
1186
- end
1187
- rescue ConfigurationError => e
1188
- # Pass through
1189
- raise e
1190
- rescue => e
1191
- @@logger.error("Failed to connect using #{@@config[:method]}")
1192
- raise e
1193
- end
1194
-
1195
- # Enforce LDAPv3
1196
- Base.connection(nil, false) do |conn|
1197
- conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
1198
- end
1199
-
1200
- # Authenticate
1201
- do_bind
1202
-
1203
- # Retrieve the schema. We need this to automagically determine attributes
1204
- exc = ConnectionError.new("Unable to retrieve schema from server (#{@@config[:method]})")
1205
- Base.connection(exc, false) do |conn|
1206
- @@schema = @@conn.schema2() if @@schema.nil?
1207
- end
1208
-
1209
- end
1210
-
1211
- # Wrapper all bind activity
1212
- def Base.do_bind()
1213
- bind_dn = @@config[:bind_format] % [@@config[:user]]
1214
- # Rough bind loop:
1215
- # Attempt 1: SASL if available
1216
- # Attempt 2: SIMPLE with credentials if password block
1217
- # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
1218
- if @@config[:try_sasl] and do_sasl_bind(bind_dn)
1219
- @@logger.info('Bound SASL')
1220
- elsif do_simple_bind(bind_dn)
1221
- @@logger.info('Bound simple')
1222
- elsif @@config[:allow_anonymous] and do_anonymous_bind(bind_dn)
1223
- @@logger.info('Bound anonymous')
1224
- else
1225
- raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0
1226
- raise AuthenticationError, 'All authentication methods exhausted.'
1227
- end
1228
-
1229
- return @@conn.bound?
1230
- end
1231
- private_class_method :do_bind
1232
-
1233
- # Base.do_anonymous_bind
1234
- #
1235
- # Bind to LDAP with the given DN, but with no password. (anonymous!)
1236
- def Base.do_anonymous_bind(bind_dn)
1237
- @@logger.info "Attempting anonymous authentication"
1238
- Base.connection(nil, false) do |conn|
1239
- conn.bind()
1240
- return true
1241
- end
1242
- return false
1243
- end
1244
- private_class_method :do_anonymous_bind
1245
-
1246
- # Base.do_simple_bind
1247
- #
1248
- # Bind to LDAP with the given DN and password
1249
- def Base.do_simple_bind(bind_dn)
1250
- # Bail if we have no password or password block
1251
- if @@config[:password_block].nil? and @@config[:password].nil?
1252
- return false
1253
- end
1254
-
1255
- # TODO: Give a warning to reconnect users with password clearing
1256
- # Get the passphrase for the first time, or anew if we aren't storing
1257
- password = ''
1258
- if not @@config[:password].nil?
1259
- password = @@config[:password]
1260
- elsif not @@config[:password_block].nil?
1261
- unless @@config[:password_block].respond_to?(:call)
1262
- @@logger.error('Skipping simple bind: ' +
1263
- ':password_block not nil or Proc object. Ignoring.')
1264
- return false
1265
- end
1266
- password = @@config[:password_block].call
1267
- else
1268
- @@logger.error('Skipping simple bind: ' +
1269
- ':password_block and :password options are empty.')
1270
- return false
1271
- end
1272
-
1273
- # Store the password for quick reference later
1274
- if @@config[:store_password]
1275
- @@config[:password] = password
1276
- elsif @@config[:store_password] == false
1277
- @@config[:password] = nil
1278
- end
1279
-
1280
- Base.connection(nil, false) do |conn|
1281
- conn.bind(bind_dn, password)
1282
- return true
1283
- end
1284
- return false
1285
- end
1286
- private_class_method :do_simple_bind
1287
-
1288
- # Base.do_sasl_bind
1289
- #
1290
- # Bind to LDAP with the given DN using any available SASL methods
1291
- def Base.do_sasl_bind(bind_dn)
1292
- # Get all SASL mechanisms
1293
- #
1294
- mechanisms = []
1295
- exc = ConnectionError.new('Root DSE query failed')
1296
- Base.connection(exc, false) do |conn|
1297
- mechanisms = conn.root_dse[0]['supportedSASLMechanisms']
1298
- end
1299
- # Use GSSAPI if available
1300
- # Currently only GSSAPI is supported with Ruby/LDAP from
1301
- # http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
1302
- # TODO: Investigate further SASL support
1303
- if mechanisms.respond_to? :member? and mechanisms.member? 'GSSAPI'
1304
- Base.connection(nil, false) do |conn|
1305
- conn.sasl_quiet = @@config[:sasl_quiet] if @@config.has_key?(:sasl_quiet)
1306
- conn.sasl_bind(bind_dn, 'GSSAPI')
1307
- return true
1308
- end
1309
- end
1310
- return false
1311
- end
1312
- private_class_method :do_sasl_bind
1313
-
1314
- # base
1315
- #
1316
- # Returns the value of self.class.base
1317
- # This is just syntactic sugar
1318
- def base
1319
-
1320
- self.class.base
1321
- end
1322
-
1323
- # ldap_scope
1324
- #
1325
- # Returns the value of self.class.ldap_scope
1326
- # This is just syntactic sugar
1327
- def ldap_scope
1328
-
1329
- self.class.ldap_scope
1330
- end
1331
-
1332
- # required_classes
1333
- #
1334
- # Returns the value of self.class.required_classes
1335
- # This is just syntactic sugar
1336
- def required_classes
1337
-
1338
- self.class.required_classes
1339
- end
1340
-
1341
- # dnattr
1342
- #
1343
- # Returns the value of self.class.dnattr
1344
- # This is just syntactic sugar
1345
- def dnattr
1346
-
1347
- self.class.dnattr
1348
- end
1349
-
1350
- # attribute_method
1351
- #
1352
- # Return the value of the attribute called by method_missing?
1353
- def attribute_method(method, not_array = false)
1354
-
1355
- attr = @attr_methods[method]
1356
-
1357
- # Set the default value to empty if attr is not set.
1358
- @data[attr] = [] if @data[attr].nil?
1359
-
1360
- # Return a copy of the stored data
1361
- return array_of(@data[attr].dup, false) if not_array
1362
- return @data[attr]
1363
- end
1364
-
1365
-
1366
- # attribute_method=
1367
- #
1368
- # Set the value of the attribute called by method_missing?
1369
- def attribute_method=(method, value)
1370
-
1371
- # Get the attr and clean up the input
1372
- attr = @attr_methods[method]
1373
-
1374
-
1375
- # Check if it is the DN attribute
1376
- if dnattr() == attr
1377
- raise AttributeAssignmentError, 'cannot modify the DN attribute value'
1378
- end
1379
-
1380
- # Enforce LDAP-pleasing values
1381
-
1382
- real_value = value
1383
- # Squash empty values
1384
- if value.class == Array
1385
- real_value = value.collect {|c| if c == ''; []; else c; end }.flatten
1386
- end
1387
- real_value = [] if real_value.nil?
1388
- real_value = [] if real_value == ''
1389
- real_value = [real_value] if real_value.class == String
1390
- real_value = [real_value.to_s] if real_value.class == Fixnum
1391
- # NOTE: Hashes are allowed for subtyping.
1392
-
1393
- # Assign the value
1394
- @data[attr] = real_value
1395
-
1396
- # Return the passed in value
1397
-
1398
- return @data[attr]
1399
- end
1400
-
1401
-
1402
- # define_attribute_methods
1403
- #
1404
- # Make a method entry for _every_ alias of a valid attribute and map it
1405
- # onto the first attribute passed in.
1406
- def define_attribute_methods(attr)
1407
-
1408
- if @attr_methods.has_key? attr
1409
- return
1410
- end
1411
- aliases = Base.schema.attribute_aliases(attr)
1412
- aliases.each do |ali|
1413
-
1414
- @attr_methods[ali] = attr
1415
-
1416
- @attr_methods[ali.downcase] = attr
1417
- end
1418
-
1419
- end
1420
-
1421
- # array_of
1422
- #
1423
- # Returns the array form of a value, or not an array if
1424
- # false is passed in.
1425
- def array_of(value, to_a = true)
1426
-
1427
- if to_a
1428
- case value.class.to_s
1429
- when 'Array'
1430
- return value
1431
- when 'Hash'
1432
- return [value]
1433
- else
1434
- return [value.to_s]
1435
- end
1436
- else
1437
- case value.class.to_s
1438
- when 'Array'
1439
- return nil if value.size == 0
1440
- return value[0] if value.size == 1
1441
- return value
1442
- when 'Hash'
1443
- return value
1444
- else
1445
- return value.to_s
1446
- end
1447
- end
1448
- end
1449
-
1450
- end # Base
1451
-
1452
- end # ActiveLDAP
1453
-
1454
-
1455
-
1456
-