ruby-activeldap 0.7.4 → 0.8.0

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