ruby-activeldap 0.7.1 → 0.7.2
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.
- data/lib/activeldap.rb +23 -4
- data/lib/activeldap/associations.rb +38 -10
- data/lib/activeldap/base.rb +235 -224
- data/lib/activeldap/configuration.rb +19 -1
- data/lib/activeldap/ldap.rb +7 -1
- data/lib/activeldap/schema2.rb +9 -3
- data/lib/activeldap/timeout.rb +75 -0
- data/lib/activeldap/timeout_stub.rb +17 -0
- metadata +4 -2
data/lib/activeldap.rb
CHANGED
@@ -177,7 +177,8 @@
|
|
177
177
|
# Below is a much more realistic Group class:
|
178
178
|
#
|
179
179
|
# class Group < ActiveLDAP::Base
|
180
|
-
# ldap_mapping :dnattr => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
|
180
|
+
# ldap_mapping :dnattr => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']<
|
181
|
+
# :scope => LDAP::LDAP_SCOPE_ONELEVEL
|
181
182
|
# end
|
182
183
|
#
|
183
184
|
# As you can see, this method is used for defining how this class maps in to LDAP. Let's say that
|
@@ -205,6 +206,8 @@
|
|
205
206
|
# :prefix |
|
206
207
|
# :base from configuration.rb
|
207
208
|
#
|
209
|
+
# :scope tells ActiveLDAP to only search under ou=Groups, and not to look deeper
|
210
|
+
# for dnattr matches. (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
|
208
211
|
#
|
209
212
|
# Something's missing: :classes. :classes is used to tell Ruby/ActiveLDAP what
|
210
213
|
# the minimum requirement is when creating a new object. LDAP uses objectClasses
|
@@ -222,7 +225,10 @@
|
|
222
225
|
#
|
223
226
|
# :classes isn't the only optional argument. If :dnattr is left off, it defaults
|
224
227
|
# to 'cn'. If :prefix is left off, it will default to 'ou=CLASSNAME'. In this
|
225
|
-
# case, it would be 'ou=Group'.
|
228
|
+
# case, it would be 'ou=Group'. There is also a :parent_class option which, when
|
229
|
+
# specified, adds a method call parent() which will return the
|
230
|
+
# parent_class.new(parent_dn). The parent_dn is the objects dn without the dnattr
|
231
|
+
# pair.
|
226
232
|
#
|
227
233
|
# :classes should be an Array. :dnattr should be a String and so should :prefix.
|
228
234
|
#
|
@@ -486,12 +492,20 @@
|
|
486
492
|
# bind methods fail
|
487
493
|
# * :try_sasl, when true, tells ActiveLDAP to attempt a SASL-GSSAPI bind
|
488
494
|
# * :sasl_quiet, when true, tells the SASL libraries to not spew messages to STDOUT
|
495
|
+
# * :method indicates whether to use :ssl, :tls, or :plain
|
496
|
+
# * :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite.
|
497
|
+
# * :retry_wait - seconds to wait before retrying a connection
|
498
|
+
# * :ldap_scope - dictates how to find objects. (Default: ONELEVEL)
|
499
|
+
# * :return_objects - indicates whether find/find_all will return objects or just the distinguished name attribute value of the matches. Rails users will find this useful.
|
500
|
+
# * :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned.
|
501
|
+
# * :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true
|
502
|
+
# See lib/configuration.rb for defaults for each option
|
489
503
|
#
|
490
504
|
# Base.connect both connects and binds in one step. It follows roughly the following approach:
|
491
505
|
#
|
492
506
|
# * Connect to host:port using :method
|
493
507
|
#
|
494
|
-
# * If user and password_block, attempt to bind with credentials.
|
508
|
+
# * If user and password_block/password, attempt to bind with credentials.
|
495
509
|
# * If that fails or no password_block and anonymous allowed, attempt to bind
|
496
510
|
# anonymously.
|
497
511
|
# * If that fails, error out.
|
@@ -907,13 +921,18 @@ $VERBOSE, verbose = false, $VERBOSE
|
|
907
921
|
|
908
922
|
require 'activeldap/ldap'
|
909
923
|
require 'activeldap/schema2'
|
924
|
+
if RUBY_PLATFORM.match('linux')
|
925
|
+
require 'activeldap/timeout'
|
926
|
+
else
|
927
|
+
require 'activeldap/timeout_stub'
|
928
|
+
end
|
910
929
|
require 'activeldap/base'
|
911
930
|
require 'activeldap/associations'
|
912
931
|
require 'activeldap/configuration'
|
913
932
|
|
914
933
|
|
915
934
|
module ActiveLDAP
|
916
|
-
VERSION = "0.7.
|
935
|
+
VERSION = "0.7.2"
|
917
936
|
end
|
918
937
|
|
919
938
|
ActiveLDAP::Base.class_eval do
|
@@ -11,28 +11,42 @@ module ActiveLDAP
|
|
11
11
|
base.extend(ClassMethods)
|
12
12
|
end
|
13
13
|
module ClassMethods
|
14
|
+
|
14
15
|
# This class function is used to setup all mappings between the subclass
|
15
16
|
# and ldap for use in activeldap
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# ldap_mapping :dnattr => 'uid', :prefix => 'ou=People',
|
20
|
+
# :classes => ['top', 'posixAccount'], scope => LDAP::LDAP_SCOPE_SUBTREE,
|
21
|
+
# :parent => String
|
16
22
|
def ldap_mapping(options = {})
|
17
23
|
# The immediate ancestor should be the caller....
|
18
24
|
klass = self.ancestors[0]
|
19
25
|
|
20
26
|
dnattr = options[:dnattr] || 'cn'
|
21
27
|
prefix = options[:prefix] || "ou=#{klass.to_s.split(':').last}"
|
22
|
-
classes_array = options[:classes] ||
|
28
|
+
classes_array = options[:classes] || nil
|
29
|
+
scope = options[:scope] || 'super'
|
30
|
+
# When used, instantiates parent objects from the "parent dn". This
|
31
|
+
# can be a String or a real ActiveLDAP class. This just adds the helper
|
32
|
+
# Base#parent.
|
33
|
+
parent = options[:parent_class] || nil
|
23
34
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
classes = 'super'
|
36
|
+
unless classes_array.nil?
|
37
|
+
raise TypeError, ":classes must be an array" \
|
38
|
+
unless classes_array.respond_to? :size
|
39
|
+
# Build classes array
|
40
|
+
classes = '['
|
41
|
+
classes_array.map! {|x| x = "'#{x}'"}
|
42
|
+
classes << classes_array.join(', ')
|
43
|
+
classes << ']'
|
44
|
+
end
|
31
45
|
|
32
46
|
# This adds the methods to the local
|
33
47
|
# class which can then be inherited, etc
|
34
48
|
# which describe the mapping to LDAP.
|
35
|
-
klass.class_eval
|
49
|
+
klass.class_eval(<<-"end_eval")
|
36
50
|
class << self
|
37
51
|
# Return the list of required object classes
|
38
52
|
def required_classes
|
@@ -52,8 +66,13 @@ module ActiveLDAP
|
|
52
66
|
def dnattr
|
53
67
|
'#{dnattr}'
|
54
68
|
end
|
55
|
-
end
|
56
69
|
|
70
|
+
# Return the expected DN attribute of an object
|
71
|
+
def ldap_scope
|
72
|
+
#{scope}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
57
76
|
# Hide connect
|
58
77
|
private_class_method :connect
|
59
78
|
|
@@ -63,6 +82,15 @@ module ActiveLDAP
|
|
63
82
|
public_class_method :new
|
64
83
|
public_class_method :dnattr
|
65
84
|
end_eval
|
85
|
+
|
86
|
+
# Add the parent helper if desired
|
87
|
+
if parent
|
88
|
+
klass.class_eval(<<-"end_eval")
|
89
|
+
def parent()
|
90
|
+
return #{parent}.new(@dn.split(',')[1..-1].join(','))
|
91
|
+
end
|
92
|
+
end_eval
|
93
|
+
end
|
66
94
|
end
|
67
95
|
|
68
96
|
# belongs_to
|
data/lib/activeldap/base.rb
CHANGED
@@ -40,7 +40,7 @@ module ActiveLDAP
|
|
40
40
|
# If no exact match, raise an error.
|
41
41
|
# If match, change all LDAP attributes in accessor attributes on the object.
|
42
42
|
# -- these are ACTUALLY populated from schema - see subschema.rb example
|
43
|
-
# -- @conn.
|
43
|
+
# -- @conn.schema2().each{|k,vs| vs.each{|v| print("#{k}: #{v}\n")}}
|
44
44
|
# -- extract objectClasses from match and populate
|
45
45
|
# Multiple entries become lists.
|
46
46
|
# If this isn't read-only then lists become multiple entries, etc.
|
@@ -94,6 +94,11 @@ module ActiveLDAP
|
|
94
94
|
class AttributeAssignmentError < RuntimeError
|
95
95
|
end
|
96
96
|
|
97
|
+
# TimeoutError
|
98
|
+
#
|
99
|
+
# An exception raised when a connection action fails due to a timeout
|
100
|
+
class TimeoutError < RuntimeError
|
101
|
+
end
|
97
102
|
|
98
103
|
# Base
|
99
104
|
#
|
@@ -196,6 +201,9 @@ module ActiveLDAP
|
|
196
201
|
# :method - whether to use :ssl, :tls, or :plain (unencrypted)
|
197
202
|
# :retry_wait - seconds to wait before retrying a connection
|
198
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
|
199
207
|
# See lib/configuration.rb for defaults for each option
|
200
208
|
def Base.connect(config={})
|
201
209
|
# Process config
|
@@ -203,10 +211,16 @@ module ActiveLDAP
|
|
203
211
|
## These will be replace by configuration.rb defaults if defined
|
204
212
|
@@config = DEFAULT_CONFIG.dup
|
205
213
|
config.keys.each do |key|
|
206
|
-
|
214
|
+
case key
|
215
|
+
when :base
|
207
216
|
# Scrub before inserting
|
208
217
|
base = config[:base].gsub(/['}{#]/, '')
|
209
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")
|
210
224
|
else
|
211
225
|
@@config[key] = config[key]
|
212
226
|
end
|
@@ -242,11 +256,79 @@ module ActiveLDAP
|
|
242
256
|
end
|
243
257
|
@@conn = nil
|
244
258
|
# Make sure it is cleaned up
|
245
|
-
|
259
|
+
# This causes Ruby/LDAP memory corruption.
|
260
|
+
# ObjectSpace.garbage_collect
|
246
261
|
end
|
247
262
|
|
248
263
|
# Return the LDAP connection object currently in use
|
249
|
-
|
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 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::ServerDown => 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
|
+
@@logger.error(exc.message) unless exc.nil?
|
303
|
+
retry if try_reconnect and Base.reconnect()
|
304
|
+
raise exc unless exc.nil?
|
305
|
+
return nil
|
306
|
+
rescue LDAP::ResultError => 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(exc.message) unless exc.nil?
|
311
|
+
retry if try_reconnect and Base.reconnect()
|
312
|
+
raise exc unless exc.nil?
|
313
|
+
return nil
|
314
|
+
rescue LDAP::UndefinedType => e
|
315
|
+
@@logger.error("#{e.class} exception occurred in connection block")
|
316
|
+
@@logger.error("Exception message: #{e.message}")
|
317
|
+
@@logger.error("Exception backtrace: #{e.backtrace}")
|
318
|
+
# Do not retry - not a connection error
|
319
|
+
raise exc unless exc.nil?
|
320
|
+
return nil
|
321
|
+
# Catch all - to be remedied later
|
322
|
+
rescue => e
|
323
|
+
@@logger.error("#{e.class} exception occurred in connection block")
|
324
|
+
@@logger.error("Exception message: #{e.message}")
|
325
|
+
@@logger.error("Exception backtrace: #{e.backtrace}")
|
326
|
+
@@logger.error("Error in catch all: please send debug log to ActiveLDAP author")
|
327
|
+
@@logger.error(exc.message) unless exc.nil?
|
328
|
+
raise exc unless exc.nil?
|
329
|
+
return nil
|
330
|
+
end
|
331
|
+
end
|
250
332
|
return @@conn
|
251
333
|
end
|
252
334
|
|
@@ -255,24 +337,36 @@ module ActiveLDAP
|
|
255
337
|
@@conn = conn
|
256
338
|
end
|
257
339
|
|
340
|
+
# Determine if we have exceed the retry limit or not.
|
341
|
+
# True is reconnecting is allowed - False if not.
|
342
|
+
def Base.can_reconnect?
|
343
|
+
# Allow connect if we've never connected.
|
344
|
+
return true unless @@config
|
345
|
+
if @@reconnect_attempts < (@@config[:retries] - 1) or
|
346
|
+
@@config[:retries] < 0
|
347
|
+
return true
|
348
|
+
end
|
349
|
+
return false
|
350
|
+
end
|
351
|
+
|
258
352
|
# Attempts to reconnect up to the number of times allowed
|
259
353
|
# If forced, try once then fail with ConnectionError if not connected.
|
260
354
|
def Base.reconnect(force=false)
|
355
|
+
unless @@config
|
356
|
+
@@logger.error('Ignoring force: Base.reconnect called before Base.connect') if force
|
357
|
+
|
358
|
+
Base.connect
|
359
|
+
return true
|
360
|
+
end
|
261
361
|
not_connected = true
|
262
362
|
while not_connected
|
263
|
-
|
264
|
-
begin
|
265
|
-
@@conn.unbind() if not @@conn.nil? and @@conn.bound?
|
266
|
-
rescue
|
267
|
-
# Ignore complaints.
|
268
|
-
end
|
269
|
-
@@conn = nil
|
270
|
-
|
271
|
-
if @@config[:retries] == -1 or force == true
|
363
|
+
if Base.can_reconnect?
|
272
364
|
|
365
|
+
Base.close()
|
273
366
|
|
274
367
|
# Reset the attempts if this was forced.
|
275
|
-
@@reconnect_attempts = 0 if
|
368
|
+
@@reconnect_attempts = 0 if force
|
369
|
+
@@reconnect_attempts += 1 if @@config[:retries] >= 0
|
276
370
|
begin
|
277
371
|
do_connect()
|
278
372
|
not_connected = false
|
@@ -282,18 +376,6 @@ module ActiveLDAP
|
|
282
376
|
# Do not loop if forced
|
283
377
|
raise ConnectionError, detail.message if force
|
284
378
|
end
|
285
|
-
elsif @@reconnect_attempts < @@config[:retries]
|
286
|
-
|
287
|
-
@@reconnect_attempts += 1
|
288
|
-
begin
|
289
|
-
do_connect()
|
290
|
-
not_connected = false
|
291
|
-
# Reset to 0 if a connection was made.
|
292
|
-
@@reconnect_attempts = 0
|
293
|
-
rescue => detail
|
294
|
-
@@logger.error("Reconnect to server failed: #{detail.exception}")
|
295
|
-
@@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}")
|
296
|
-
end
|
297
379
|
else
|
298
380
|
# Raise a warning
|
299
381
|
raise ConnectionError, 'Giving up trying to reconnect to LDAP server.'
|
@@ -315,13 +397,7 @@ module ActiveLDAP
|
|
315
397
|
# Wraps Ruby/LDAP connection.search to make it easier to search for specific
|
316
398
|
# data without cracking open Base.connection
|
317
399
|
def Base.search(config={})
|
318
|
-
|
319
|
-
if @@config
|
320
|
-
ActiveLDAP::Base.connect(@@config)
|
321
|
-
else
|
322
|
-
ActiveLDAP::Base.connect
|
323
|
-
end
|
324
|
-
end
|
400
|
+
Base.reconnect if Base.connection.nil? and Base.can_reconnect?
|
325
401
|
|
326
402
|
config[:filter] = 'objectClass=*' unless config.has_key? :filter
|
327
403
|
config[:attrs] = [] unless config.has_key? :attrs
|
@@ -331,8 +407,8 @@ module ActiveLDAP
|
|
331
407
|
values = []
|
332
408
|
config[:attrs] = config[:attrs].to_a # just in case
|
333
409
|
|
334
|
-
|
335
|
-
|
410
|
+
result = Base.connection() do |conn|
|
411
|
+
conn.search(config[:base], config[:scope], config[:filter], config[:attrs]) do |m|
|
336
412
|
res = {}
|
337
413
|
res['dn'] = [m.dn.dup] # For consistency with the below
|
338
414
|
m.attrs.each do |attr|
|
@@ -342,18 +418,10 @@ module ActiveLDAP
|
|
342
418
|
end
|
343
419
|
values.push(res)
|
344
420
|
end
|
345
|
-
|
346
|
-
|
347
|
-
# The connection may have gone stale. Let's reconnect and retry.
|
348
|
-
retry if Base.reconnect()
|
421
|
+
end
|
422
|
+
if result.nil?
|
349
423
|
# Do nothing on failure
|
350
424
|
|
351
|
-
rescue => detail
|
352
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
353
|
-
|
354
|
-
retry if Base.reconnect()
|
355
|
-
end
|
356
|
-
raise detail
|
357
425
|
end
|
358
426
|
return values
|
359
427
|
end
|
@@ -365,14 +433,8 @@ module ActiveLDAP
|
|
365
433
|
# usage: Subclass.find(:attribute => "cn", :value => "some*val", :objects => true)
|
366
434
|
# Subclass.find('some*val')
|
367
435
|
#
|
368
|
-
def Base.find(config
|
369
|
-
|
370
|
-
if @@config
|
371
|
-
ActiveLDAP::Base.connect(@@config)
|
372
|
-
else
|
373
|
-
ActiveLDAP::Base.connect
|
374
|
-
end
|
375
|
-
end
|
436
|
+
def Base.find(config='*')
|
437
|
+
Base.reconnect if Base.connection.nil? and Base.can_reconnect?
|
376
438
|
|
377
439
|
if self.class == Class
|
378
440
|
klass = self.ancestors[0].to_s.split(':').last
|
@@ -384,20 +446,18 @@ module ActiveLDAP
|
|
384
446
|
|
385
447
|
# Allow a single string argument
|
386
448
|
attr = dnattr()
|
387
|
-
objects =
|
449
|
+
objects = @@config[:return_objects]
|
388
450
|
val = config
|
389
451
|
# Or a hash
|
390
|
-
if config.respond_to?
|
452
|
+
if config.respond_to?(:has_key?)
|
391
453
|
attr = config[:attribute] || dnattr()
|
392
454
|
val = config[:value] || '*'
|
393
|
-
objects = config[:objects]
|
455
|
+
objects = config[:objects] || @@config[:return_objects]
|
394
456
|
end
|
395
457
|
|
396
|
-
|
397
|
-
|
398
|
-
begin
|
458
|
+
Base.connection(ConnectionError.new("Failed in #{self.class}#find(#{config.inspect})")) do |conn|
|
399
459
|
# Get some attributes
|
400
|
-
|
460
|
+
conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
|
401
461
|
# Extract the dnattr value
|
402
462
|
dnval = m.dn.split(/,/)[0].split(/=/)[1]
|
403
463
|
|
@@ -407,20 +467,8 @@ module ActiveLDAP
|
|
407
467
|
return dnval
|
408
468
|
end
|
409
469
|
end
|
410
|
-
rescue RuntimeError => detail
|
411
|
-
#TODO# Check for 'No message' when retrying
|
412
|
-
# The connection may have gone stale. Let's reconnect and retry.
|
413
|
-
retry if Base.reconnect()
|
414
|
-
|
415
|
-
# Do nothing on failure
|
416
|
-
|
417
|
-
rescue => detail
|
418
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
419
|
-
|
420
|
-
retry if Base.reconnect()
|
421
|
-
end
|
422
|
-
raise detail
|
423
470
|
end
|
471
|
+
# If we're here, there were no results
|
424
472
|
return nil
|
425
473
|
end
|
426
474
|
private_class_method :find
|
@@ -429,14 +477,8 @@ module ActiveLDAP
|
|
429
477
|
#
|
430
478
|
# Finds all matches for value where |value| is the value of some
|
431
479
|
# |field|, or the wildcard match. This is only useful for derived classes.
|
432
|
-
def Base.find_all(config
|
433
|
-
|
434
|
-
if @@config
|
435
|
-
ActiveLDAP::Base.connect(@@config)
|
436
|
-
else
|
437
|
-
ActiveLDAP::Base.connect
|
438
|
-
end
|
439
|
-
end
|
480
|
+
def Base.find_all(config='*')
|
481
|
+
Base.reconnect if Base.connection.nil? and Base.can_reconnect?
|
440
482
|
|
441
483
|
if self.class == Class
|
442
484
|
real_klass = self.ancestors[0]
|
@@ -447,20 +489,18 @@ module ActiveLDAP
|
|
447
489
|
# Allow a single string argument
|
448
490
|
val = config
|
449
491
|
attr = dnattr()
|
450
|
-
objects =
|
492
|
+
objects = @@config[:return_objects]
|
451
493
|
# Or a hash
|
452
|
-
if config.respond_to?
|
494
|
+
if config.respond_to?(:has_key?)
|
453
495
|
val = config[:value] || '*'
|
454
496
|
attr = config[:attribute] || dnattr()
|
455
|
-
objects = config[:objects]
|
497
|
+
objects = config[:objects] || @@config[:return_objects]
|
456
498
|
end
|
457
499
|
|
458
500
|
matches = []
|
459
|
-
|
460
|
-
begin
|
501
|
+
Base.connection(ConnectionError.new("Failed in #{self.class}#find_all(#{config.inspect})")) do |conn|
|
461
502
|
# Get some attributes
|
462
|
-
|
463
|
-
"(#{attr}=#{val})") do |m|
|
503
|
+
conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
|
464
504
|
# Extract the dnattr value
|
465
505
|
dnval = m.dn.split(/,/)[0].split(/=/)[1]
|
466
506
|
|
@@ -470,19 +510,6 @@ module ActiveLDAP
|
|
470
510
|
matches.push(dnval)
|
471
511
|
end
|
472
512
|
end
|
473
|
-
rescue RuntimeError => detail
|
474
|
-
#TODO# Check for 'No message' when retrying
|
475
|
-
# The connection may have gone stale. Let's reconnect and retry.
|
476
|
-
retry if Base.reconnect()
|
477
|
-
|
478
|
-
# Do nothing on failure
|
479
|
-
|
480
|
-
rescue => detail
|
481
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
482
|
-
|
483
|
-
retry if Base.reconnect()
|
484
|
-
end
|
485
|
-
raise detail
|
486
513
|
end
|
487
514
|
return matches
|
488
515
|
end
|
@@ -499,7 +526,7 @@ module ActiveLDAP
|
|
499
526
|
# configuration.rb into this class.
|
500
527
|
# When subclassing, the specified prefix will be concatenated.
|
501
528
|
def Base.base
|
502
|
-
'dc=
|
529
|
+
'dc=localdomain'
|
503
530
|
end
|
504
531
|
|
505
532
|
# Base.dnattr
|
@@ -526,6 +553,17 @@ module ActiveLDAP
|
|
526
553
|
[]
|
527
554
|
end
|
528
555
|
|
556
|
+
# Base.ldap_scope
|
557
|
+
#
|
558
|
+
# This method when included into Base provides
|
559
|
+
# an inheritable, overwritable configuration setting
|
560
|
+
#
|
561
|
+
# This value should be the default LDAP scope behavior
|
562
|
+
# desired.
|
563
|
+
def Base.ldap_scope
|
564
|
+
LDAP::LDAP_SCOPE_ONELEVEL
|
565
|
+
end
|
566
|
+
|
529
567
|
|
530
568
|
|
531
569
|
### All instance methods, etc
|
@@ -539,20 +577,12 @@ module ActiveLDAP
|
|
539
577
|
# val can be a dn attribute value, a full DN, or a LDAP::Entry. The use
|
540
578
|
# with a LDAP::Entry is primarily meant for internal use by find and
|
541
579
|
# find_all.
|
580
|
+
#
|
542
581
|
def initialize(val)
|
543
582
|
@exists = false
|
544
|
-
#
|
545
|
-
if Base.connection.nil? and
|
546
|
-
|
547
|
-
if @@config
|
548
|
-
ActiveLDAP::Base.reconnect
|
549
|
-
else
|
550
|
-
ActiveLDAP::Base.connect
|
551
|
-
end
|
552
|
-
elsif Base.connection.nil?
|
553
|
-
@@logger.error('Attempted to initialize a new object with no connection')
|
554
|
-
raise ConnectionError, 'Number of reconnect attempts exceeded.'
|
555
|
-
end
|
583
|
+
# Make sure we're connected
|
584
|
+
Base.reconnect if Base.connection.nil? and Base.can_reconnect?
|
585
|
+
|
556
586
|
if val.class == LDAP::Entry
|
557
587
|
# Call import, which is basically initialize
|
558
588
|
# without accessing LDAP.
|
@@ -569,11 +599,11 @@ module ActiveLDAP
|
|
569
599
|
@attr_methods = {} # list of valid method calls for attributes used for dereferencing
|
570
600
|
@last_oc = false # for use in other methods for "caching"
|
571
601
|
if dnattr().empty?
|
572
|
-
raise
|
602
|
+
raise ConfigurationError, "dnattr() not set for this class: #{self.class}"
|
573
603
|
end
|
574
604
|
|
575
|
-
#
|
576
|
-
if val.match(/^#{dnattr()}=([^,=]+)
|
605
|
+
# Extract dnattr if val looks like a dn
|
606
|
+
if val.match(/^#{dnattr()}=([^,=]+),/i)
|
577
607
|
val = $1
|
578
608
|
elsif val.match(/[=,]/)
|
579
609
|
@@logger.info "initialize: Changing val from '#{val}' to '' because it doesn't match the DN."
|
@@ -589,9 +619,9 @@ module ActiveLDAP
|
|
589
619
|
@dn = "#{dnattr()}=#{val},#{base()}"
|
590
620
|
|
591
621
|
# Search for the existing entry
|
592
|
-
|
622
|
+
Base.connection(ConnectionError.new("Failed in #{self.class}#new(#{val.inspect})")) do |conn|
|
593
623
|
# Get some attributes
|
594
|
-
|
624
|
+
conn.search(base(), ldap_scope(), "(#{dnattr()}=#{val})") do |m|
|
595
625
|
@exists = true
|
596
626
|
# Save DN
|
597
627
|
@dn = m.dn
|
@@ -612,20 +642,22 @@ module ActiveLDAP
|
|
612
642
|
end
|
613
643
|
end
|
614
644
|
end
|
615
|
-
rescue RuntimeError => detail
|
616
|
-
#TODO# Check for 'No message' when retrying
|
617
|
-
# The connection may have gone stale. Let's reconnect and retry.
|
618
|
-
retry if Base.reconnect()
|
619
|
-
|
620
|
-
# Do nothing on failure
|
621
|
-
@@logger.error('new: unable to search for entry')
|
622
|
-
raise detail
|
623
|
-
rescue LDAP::ResultError
|
624
645
|
end
|
625
646
|
end
|
626
647
|
|
627
648
|
# Do the actual object setup work.
|
628
649
|
if @exists
|
650
|
+
# Make sure the server uses objectClass and not objectclass
|
651
|
+
unless @ldap_data.has_key?('objectClass')
|
652
|
+
real_objc = @ldap_data.grep(/^objectclass$/i)
|
653
|
+
if real_objc.size == 1
|
654
|
+
@ldap_data['objectClass'] = @ldap_data[real_objc]
|
655
|
+
@ldap_data.delete(real_objc)
|
656
|
+
else
|
657
|
+
raise AttributeEmpty, 'objectClass was not sent by LDAP server!'
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
629
661
|
# Populate schema data
|
630
662
|
send(:apply_objectclass, @ldap_data['objectClass'])
|
631
663
|
|
@@ -633,6 +665,10 @@ module ActiveLDAP
|
|
633
665
|
@ldap_data.each do |pair|
|
634
666
|
real_attr = @attr_methods[pair[0]]
|
635
667
|
|
668
|
+
if real_attr.nil?
|
669
|
+
@@logger.error("Unable to resolve attribute value #{pair[0].inspect}. " +
|
670
|
+
"Unpredictable behavior likely!")
|
671
|
+
end
|
636
672
|
@data[real_attr] = pair[1].dup
|
637
673
|
|
638
674
|
end
|
@@ -656,7 +692,7 @@ module ActiveLDAP
|
|
656
692
|
def attributes
|
657
693
|
|
658
694
|
send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc
|
659
|
-
return @attr_methods.keys
|
695
|
+
return @attr_methods.keys.map {|x|x.downcase}.uniq
|
660
696
|
end
|
661
697
|
|
662
698
|
# exists?
|
@@ -706,7 +742,8 @@ module ActiveLDAP
|
|
706
742
|
# Make sure all MUST attributes have a value
|
707
743
|
@data['objectClass'].each do |objc|
|
708
744
|
@must.each do |req_attr|
|
709
|
-
|
745
|
+
# Downcase to ensure we catch schema problems
|
746
|
+
deref = @attr_methods[req_attr.downcase]
|
710
747
|
# Set default if it wasn't yet set.
|
711
748
|
@data[deref] = [] if @data[deref].nil?
|
712
749
|
# Check for missing requirements.
|
@@ -724,20 +761,10 @@ module ActiveLDAP
|
|
724
761
|
# Delete this entry from LDAP
|
725
762
|
def delete
|
726
763
|
|
727
|
-
|
728
|
-
|
764
|
+
Base.connection(DeleteError.new(
|
765
|
+
"Failed to delete LDAP entry: '#{@dn}'")) do |conn|
|
766
|
+
conn.delete(@dn)
|
729
767
|
@exists = false
|
730
|
-
rescue RuntimeError => detail
|
731
|
-
#todo# check for 'no message' when retrying
|
732
|
-
# the connection may have gone stale. let's reconnect and retry.
|
733
|
-
retry if Base.reconnect()
|
734
|
-
raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
|
735
|
-
rescue LDAP::ResultError => detail
|
736
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
737
|
-
|
738
|
-
retry if Base.reconnect()
|
739
|
-
end
|
740
|
-
raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
|
741
768
|
end
|
742
769
|
end
|
743
770
|
|
@@ -784,6 +811,15 @@ module ActiveLDAP
|
|
784
811
|
|
785
812
|
data = Marshal.load(Marshal.dump(@data))
|
786
813
|
|
814
|
+
|
815
|
+
|
816
|
+
bad_attrs = @data.keys - (@must+@may)
|
817
|
+
bad_attrs.each do |removeme|
|
818
|
+
data.delete(removeme)
|
819
|
+
end
|
820
|
+
|
821
|
+
|
822
|
+
|
787
823
|
|
788
824
|
data.keys.each do |key|
|
789
825
|
data[key].each do |value|
|
@@ -800,7 +836,6 @@ module ActiveLDAP
|
|
800
836
|
end
|
801
837
|
|
802
838
|
|
803
|
-
|
804
839
|
if @exists
|
805
840
|
# Cycle through all attrs to determine action
|
806
841
|
action = {}
|
@@ -865,23 +900,11 @@ module ActiveLDAP
|
|
865
900
|
end
|
866
901
|
end
|
867
902
|
|
868
|
-
|
903
|
+
Base.connection(WriteError.new(
|
904
|
+
"Failed to modify: '#{entry}'")) do |conn|
|
869
905
|
|
870
|
-
|
906
|
+
conn.modify(@dn, entry)
|
871
907
|
|
872
|
-
rescue RuntimeError => detail
|
873
|
-
#todo# check for SERVER_DOWN
|
874
|
-
# the connection may have gone stale. let's reconnect and retry.
|
875
|
-
retry if Base.reconnect()
|
876
|
-
raise WriteError, "Could not update LDAP entry: #{detail}"
|
877
|
-
rescue => detail
|
878
|
-
|
879
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
880
|
-
|
881
|
-
retry if Base.reconnect()
|
882
|
-
end
|
883
|
-
|
884
|
-
raise WriteError, "Could not update LDAP entry: #{detail}"
|
885
908
|
end
|
886
909
|
else # add everything!
|
887
910
|
|
@@ -903,30 +926,26 @@ module ActiveLDAP
|
|
903
926
|
entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD|binary, pair[0], pair[1]))
|
904
927
|
end
|
905
928
|
end
|
906
|
-
|
929
|
+
Base.connection(WriteError.new(
|
930
|
+
"Failed to add: '#{entry}'")) do |conn|
|
907
931
|
|
908
|
-
|
932
|
+
conn.add(@dn, entry)
|
909
933
|
|
910
934
|
@exists = true
|
911
|
-
rescue RuntimeError => detail
|
912
|
-
# The connection may have gone stale. Let's reconnect and retry.
|
913
|
-
retry if Base.reconnect()
|
914
|
-
raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}"
|
915
|
-
rescue LDAP::ResultError => detail
|
916
|
-
if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
|
917
|
-
|
918
|
-
retry if Base.reconnect()
|
919
|
-
end
|
920
|
-
raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}"
|
921
935
|
end
|
922
936
|
end
|
923
937
|
|
924
|
-
@ldap_data = Marshal.load(Marshal.dump(
|
938
|
+
@ldap_data = Marshal.load(Marshal.dump(data))
|
939
|
+
# Delete items disallowed by objectclasses.
|
940
|
+
# They should have been removed from ldap.
|
941
|
+
|
942
|
+
bad_attrs.each do |removeme|
|
943
|
+
@ldap_data.delete(removeme)
|
944
|
+
end
|
925
945
|
|
926
946
|
|
927
947
|
end
|
928
948
|
|
929
|
-
|
930
949
|
# method_missing
|
931
950
|
#
|
932
951
|
# If a given method matches an attribute or an attribute alias
|
@@ -970,7 +989,7 @@ module ActiveLDAP
|
|
970
989
|
end
|
971
990
|
|
972
991
|
|
973
|
-
|
992
|
+
private
|
974
993
|
|
975
994
|
# import(LDAP::Entry)
|
976
995
|
#
|
@@ -1072,14 +1091,6 @@ module ActiveLDAP
|
|
1072
1091
|
# Update attr_method with appropriate
|
1073
1092
|
define_attribute_methods(attr)
|
1074
1093
|
end
|
1075
|
-
|
1076
|
-
# Delete all now innew_ocid attributes given the new objectClasses
|
1077
|
-
@data.keys.each do |key|
|
1078
|
-
# If it's not a proper aliased attribute, drop it
|
1079
|
-
unless @attr_methods.has_key? key
|
1080
|
-
@data.delete(key)
|
1081
|
-
end
|
1082
|
-
end
|
1083
1094
|
end
|
1084
1095
|
|
1085
1096
|
|
@@ -1198,21 +1209,18 @@ module ActiveLDAP
|
|
1198
1209
|
end
|
1199
1210
|
|
1200
1211
|
# Enforce LDAPv3
|
1201
|
-
|
1212
|
+
Base.connection(nil, false) do |conn|
|
1213
|
+
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
|
1214
|
+
end
|
1202
1215
|
|
1203
1216
|
# Authenticate
|
1204
1217
|
do_bind
|
1205
1218
|
|
1206
1219
|
# Retrieve the schema. We need this to automagically determine attributes
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
@@logger.error("Failed to retrieve the schema (#{@@config[:method]})")
|
1211
|
-
@@logger.error("Schema failure exception: #{e.exception}")
|
1212
|
-
@@logger.error("Schema failure backtrace: #{e.backtrace}")
|
1213
|
-
raise ConnectionError, "#{e.exception} - LDAP connection failure, or server does not support schema queries."
|
1220
|
+
exc = ConnectionError.new("Unable to retrieve schema from server (#{@@config[:method]})")
|
1221
|
+
Base.connection(exc, false) do |conn|
|
1222
|
+
@@schema = @@conn.schema2() if @@schema.nil?
|
1214
1223
|
end
|
1215
|
-
|
1216
1224
|
|
1217
1225
|
end
|
1218
1226
|
|
@@ -1228,39 +1236,35 @@ module ActiveLDAP
|
|
1228
1236
|
elsif do_simple_bind(bind_dn)
|
1229
1237
|
@@logger.info('Bound simple')
|
1230
1238
|
elsif @@config[:allow_anonymous] and do_anonymous_bind(bind_dn)
|
1231
|
-
@@logger.info('Bound
|
1239
|
+
@@logger.info('Bound anonymous')
|
1232
1240
|
else
|
1233
|
-
@@logger.error('Failed to bind using any available method')
|
1234
1241
|
raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0
|
1242
|
+
raise AuthenticationError, 'All authentication methods exhausted.'
|
1235
1243
|
end
|
1236
1244
|
|
1237
1245
|
return @@conn.bound?
|
1238
1246
|
end
|
1247
|
+
private_class_method :do_bind
|
1239
1248
|
|
1240
1249
|
# Base.do_anonymous_bind
|
1241
1250
|
#
|
1242
1251
|
# Bind to LDAP with the given DN, but with no password. (anonymous!)
|
1243
1252
|
def Base.do_anonymous_bind(bind_dn)
|
1244
1253
|
@@logger.info "Attempting anonymous authentication"
|
1245
|
-
|
1246
|
-
|
1254
|
+
Base.connection(nil, false) do |conn|
|
1255
|
+
conn.bind()
|
1247
1256
|
return true
|
1248
|
-
rescue => e
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
@@logger.warn "Warning: Anonymous authentication failed."
|
1253
|
-
@@logger.warn "message: #{e.message}"
|
1254
|
-
return false
|
1255
1257
|
end
|
1258
|
+
return false
|
1256
1259
|
end
|
1260
|
+
private_class_method :do_anonymous_bind
|
1257
1261
|
|
1258
1262
|
# Base.do_simple_bind
|
1259
1263
|
#
|
1260
1264
|
# Bind to LDAP with the given DN and password
|
1261
1265
|
def Base.do_simple_bind(bind_dn)
|
1262
1266
|
# Bail if we have no password or password block
|
1263
|
-
if
|
1267
|
+
if @@config[:password_block].nil? and @@config[:password].nil?
|
1264
1268
|
return false
|
1265
1269
|
end
|
1266
1270
|
|
@@ -1282,50 +1286,46 @@ module ActiveLDAP
|
|
1282
1286
|
return false
|
1283
1287
|
end
|
1284
1288
|
|
1285
|
-
begin
|
1286
|
-
@@conn.bind(bind_dn, password)
|
1287
|
-
rescue => e
|
1288
|
-
|
1289
|
-
# TODO: replace this with LDAP::err2exception()
|
1290
|
-
if @@conn.err == LDAP::LDAP_SERVER_DOWN
|
1291
|
-
@@logger.error "Warning: " + e.message
|
1292
|
-
else
|
1293
|
-
@@logger.warn "Warning: SIMPLE authentication failed."
|
1294
|
-
end
|
1295
|
-
return false
|
1296
|
-
end
|
1297
1289
|
# Store the password for quick reference later
|
1298
1290
|
if @@config[:store_password]
|
1299
1291
|
@@config[:password] = password
|
1300
1292
|
elsif @@config[:store_password] == false
|
1301
1293
|
@@config[:password] = nil
|
1302
1294
|
end
|
1303
|
-
|
1295
|
+
|
1296
|
+
Base.connection(nil, false) do |conn|
|
1297
|
+
conn.bind(bind_dn, password)
|
1298
|
+
return true
|
1299
|
+
end
|
1300
|
+
return false
|
1304
1301
|
end
|
1302
|
+
private_class_method :do_simple_bind
|
1305
1303
|
|
1306
1304
|
# Base.do_sasl_bind
|
1307
1305
|
#
|
1308
1306
|
# Bind to LDAP with the given DN using any available SASL methods
|
1309
1307
|
def Base.do_sasl_bind(bind_dn)
|
1310
1308
|
# Get all SASL mechanisms
|
1311
|
-
|
1309
|
+
#
|
1310
|
+
mechanisms = []
|
1311
|
+
exc = ConnectionError.new('Root DSE query failed')
|
1312
|
+
Base.connection(exc, false) do |conn|
|
1313
|
+
mechanisms = conn.root_dse[0]['supportedSASLMechanisms']
|
1314
|
+
end
|
1312
1315
|
# Use GSSAPI if available
|
1313
1316
|
# Currently only GSSAPI is supported with Ruby/LDAP from
|
1314
1317
|
# http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
|
1315
1318
|
# TODO: Investigate further SASL support
|
1316
1319
|
if mechanisms.respond_to? :member? and mechanisms.member? 'GSSAPI'
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
+
Base.connection(nil, false) do |conn|
|
1321
|
+
conn.sasl_quiet = @@config[:sasl_quiet] if @@config.has_key?(:sasl_quiet)
|
1322
|
+
conn.sasl_bind(bind_dn, 'GSSAPI')
|
1320
1323
|
return true
|
1321
|
-
rescue
|
1322
|
-
|
1323
|
-
@@logger.warn "Warning: SASL GSSAPI authentication failed."
|
1324
|
-
return false
|
1325
1324
|
end
|
1326
1325
|
end
|
1327
1326
|
return false
|
1328
1327
|
end
|
1328
|
+
private_class_method :do_sasl_bind
|
1329
1329
|
|
1330
1330
|
# base
|
1331
1331
|
#
|
@@ -1336,6 +1336,15 @@ module ActiveLDAP
|
|
1336
1336
|
self.class.base
|
1337
1337
|
end
|
1338
1338
|
|
1339
|
+
# ldap_scope
|
1340
|
+
#
|
1341
|
+
# Returns the value of self.class.ldap_scope
|
1342
|
+
# This is just syntactic sugar
|
1343
|
+
def ldap_scope
|
1344
|
+
|
1345
|
+
self.class.ldap_scope
|
1346
|
+
end
|
1347
|
+
|
1339
1348
|
# required_classes
|
1340
1349
|
#
|
1341
1350
|
# Returns the value of self.class.required_classes
|
@@ -1419,6 +1428,8 @@ module ActiveLDAP
|
|
1419
1428
|
aliases.each do |ali|
|
1420
1429
|
|
1421
1430
|
@attr_methods[ali] = attr
|
1431
|
+
|
1432
|
+
@attr_methods[ali.downcase] = attr
|
1422
1433
|
end
|
1423
1434
|
|
1424
1435
|
end
|
@@ -22,10 +22,28 @@ module ActiveLDAP
|
|
22
22
|
|
23
23
|
DEFAULT_CONFIG[:retries] = 3
|
24
24
|
DEFAULT_CONFIG[:retry_wait] = 3
|
25
|
+
DEFAULT_CONFIG[:timeout] = 0 # in seconds; 0 <= Never timeout
|
26
|
+
# Whether or not to retry on timeouts
|
27
|
+
DEFAULT_CONFIG[:retry_on_timeout] = true
|
28
|
+
|
29
|
+
# Whether to return objects by default from find/find_all
|
30
|
+
DEFAULT_CONFIG[:return_objects] = false
|
25
31
|
|
26
32
|
DEFAULT_CONFIG[:logger] = nil
|
27
|
-
DEFAULT_CONFIG[:ldap_scope] = LDAP::LDAP_SCOPE_ONELEVEL
|
28
33
|
|
34
|
+
# On connect, this is overriden by the :base argument
|
35
|
+
#
|
36
|
+
# Set this to LDAP_SCOPE_SUBTREE if you have a LDAP tree where all
|
37
|
+
# objects of the same class living in different parts of the same subtree, but
|
38
|
+
# not. LDAP_SCOPE_ONELEVEL is for use when all the objects in your classes live
|
39
|
+
# under one shared level (e.g. ou=People,dc=localdomain)
|
40
|
+
#
|
41
|
+
# This can be overriden on a per class basis in ldap_mapping :scope
|
42
|
+
def Base.ldap_scope
|
43
|
+
LDAP::LDAP_SCOPE_ONELEVEL
|
44
|
+
end
|
45
|
+
|
46
|
+
# On connect, this is overriden by the :base argument
|
29
47
|
# Make the return value the string that is your LDAP base
|
30
48
|
def Base.base
|
31
49
|
'dc=localdomain'
|
data/lib/activeldap/ldap.rb
CHANGED
@@ -6,6 +6,9 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
module LDAP
|
9
|
+
class PrettyError < RuntimeError
|
10
|
+
end
|
11
|
+
|
9
12
|
ERRORS = [
|
10
13
|
"LDAP_SUCCESS",
|
11
14
|
"LDAP_OPERATIONS_ERROR",
|
@@ -75,7 +78,7 @@ module LDAP
|
|
75
78
|
exc = exc.split('_').collect {|w| w.capitalize }.join('')
|
76
79
|
# Doesn't exist :-)
|
77
80
|
LDAP.module_eval(<<-end_module_eval)
|
78
|
-
class #{exc} < LDAP::
|
81
|
+
class #{exc} < LDAP::PrettyError
|
79
82
|
end
|
80
83
|
end_module_eval
|
81
84
|
hash[val] = exc
|
@@ -103,3 +106,6 @@ module LDAP
|
|
103
106
|
return [exc, err2string(errno)]
|
104
107
|
end
|
105
108
|
end
|
109
|
+
|
110
|
+
# Generate!
|
111
|
+
LDAP::generate_err2exceptions()
|
data/lib/activeldap/schema2.rb
CHANGED
@@ -18,6 +18,8 @@ module LDAP
|
|
18
18
|
return [] if type.empty?
|
19
19
|
return [] if at.empty?
|
20
20
|
|
21
|
+
type = type.downcase # We're going case insensitive.
|
22
|
+
|
21
23
|
# Check already parsed options first
|
22
24
|
if @@attr_cache.has_key? sub \
|
23
25
|
and @@attr_cache[sub].has_key? type \
|
@@ -38,9 +40,13 @@ module LDAP
|
|
38
40
|
self[sub].each do |s|
|
39
41
|
line = ''
|
40
42
|
if type[0..0] =~ /[0-9]/
|
41
|
-
|
43
|
+
if s =~ /\(\s+(?i:#{type})\s+(?:[A-Z]|\))/
|
44
|
+
line = s
|
45
|
+
end
|
42
46
|
else
|
43
|
-
|
47
|
+
if s =~ /NAME\s+\(?.*'(?i:#{type})'.*\)?\s+(?:[A-Z]|\))/
|
48
|
+
line = s
|
49
|
+
end
|
44
50
|
end
|
45
51
|
|
46
52
|
# I need to check, but I think some of these matchs
|
@@ -190,7 +196,7 @@ module LDAP
|
|
190
196
|
end # Schema2
|
191
197
|
|
192
198
|
class Conn
|
193
|
-
def
|
199
|
+
def schema2(base = nil, attrs = nil, sec = 0, usec = 0)
|
194
200
|
attrs ||= [
|
195
201
|
'objectClasses',
|
196
202
|
'attributeTypes',
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Timeout
|
4
|
+
|
5
|
+
# A forking timeout implementation that relies on
|
6
|
+
# signals to interrupt blocking I/O instead of passing
|
7
|
+
# that code to run in a separate process.
|
8
|
+
#
|
9
|
+
# A process is fork()ed, sleeps for _sec_,
|
10
|
+
# then sends a ALRM signal to the Process.ppid
|
11
|
+
# process. ALRM is used to avoid conflicts with sleep()
|
12
|
+
#
|
13
|
+
# This overwrites any signal
|
14
|
+
def Timeout.alarm(sec, exception=Timeout::Error, &block)
|
15
|
+
return block.call if sec == nil or sec.zero?
|
16
|
+
|
17
|
+
|
18
|
+
# Trap an alarm in case it comes before we're ready
|
19
|
+
orig_alrm = trap(:ALRM, 'IGNORE')
|
20
|
+
|
21
|
+
# Setup a fallback in case of a race condition of an
|
22
|
+
# alarm before we set the other trap
|
23
|
+
trap(:ALRM) do
|
24
|
+
# Don't leave zombies
|
25
|
+
Process.wait2()
|
26
|
+
# Restore the original handler
|
27
|
+
trap('ALRM', orig_alrm)
|
28
|
+
# Now raise an exception!
|
29
|
+
raise exception, 'execution expired'
|
30
|
+
end
|
31
|
+
|
32
|
+
# Spawn the sleeper
|
33
|
+
pid = Process.fork {
|
34
|
+
begin
|
35
|
+
# Sleep x seconds then send SIGALRM
|
36
|
+
sleep(sec)
|
37
|
+
# Send alarm!
|
38
|
+
Process.kill(:ALRM, Process.ppid)
|
39
|
+
end
|
40
|
+
exit! 0
|
41
|
+
}
|
42
|
+
|
43
|
+
# Setup the real handler
|
44
|
+
trap(:ALRM) do
|
45
|
+
# Make sure we clean up any zombies
|
46
|
+
Process.waitpid(pid)
|
47
|
+
# Restore the original handler
|
48
|
+
trap(:ALRM, orig_alrm)
|
49
|
+
# Now raise an exception!
|
50
|
+
raise exception, 'execution expired'
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
# Run the code!
|
55
|
+
return block.call
|
56
|
+
ensure
|
57
|
+
# Restore old alarm handler since we're done
|
58
|
+
trap(:ALRM, orig_alrm)
|
59
|
+
# Make sure the process is dead
|
60
|
+
# This may be run twice (trap occurs during execution) so ignore ESRCH
|
61
|
+
Process.kill(:TERM, pid) rescue Errno::ESRCH
|
62
|
+
# Don't leave zombies
|
63
|
+
Process.waitpid(pid) rescue Errno::ECHILD
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end # Timeout
|
67
|
+
|
68
|
+
if __FILE__ == $0
|
69
|
+
require 'time'
|
70
|
+
Timeout.alarm(2) do
|
71
|
+
loop do
|
72
|
+
p Time.now
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Timeout
|
4
|
+
# STUB
|
5
|
+
def Timeout.alarm(sec, exception=Timeout::Error, &block)
|
6
|
+
return block.call
|
7
|
+
end
|
8
|
+
end # Timeout
|
9
|
+
|
10
|
+
if __FILE__ == $0
|
11
|
+
require 'time'
|
12
|
+
Timeout.alarm(2) do
|
13
|
+
loop do
|
14
|
+
p Time.now
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruby-activeldap
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.7.
|
7
|
-
date: 2006-05-
|
6
|
+
version: 0.7.2
|
7
|
+
date: 2006-05-22 00:00:00 +01:00
|
8
8
|
summary: Ruby/ActiveLDAP is a object-oriented API to LDAP
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -35,6 +35,8 @@ files:
|
|
35
35
|
- lib/activeldap/configuration.rb
|
36
36
|
- lib/activeldap/ldap.rb
|
37
37
|
- lib/activeldap/schema2.rb
|
38
|
+
- lib/activeldap/timeout.rb
|
39
|
+
- lib/activeldap/timeout_stub.rb
|
38
40
|
test_files: []
|
39
41
|
|
40
42
|
rdoc_options: []
|