| Class | ActiveLdap::Adapter::Base |
| In: |
lib/active_ldap/adapter/net_ldap.rb
lib/active_ldap/adapter/jndi.rb lib/active_ldap/adapter/ldap.rb lib/active_ldap/adapter/base.rb |
| Parent: | Object |
| VALID_ADAPTER_CONFIGURATION_KEYS | = | [:host, :port, :method, :timeout, :retry_on_timeout, :retry_limit, :retry_wait, :bind_dn, :password, :password_block, :try_sasl, :sasl_mechanisms, :sasl_quiet, :allow_anonymous, :store_password, :scope] |
| LOGICAL_OPERATORS | = | [:and, :or, :not, :&, :|] |
| runtime | [R] |
# File lib/active_ldap/adapter/jndi.rb, line 7
7: def jndi_connection(options)
8: require 'active_ldap/adapter/jndi_connection'
9: Jndi.new(options)
10: end
# File lib/active_ldap/adapter/ldap.rb, line 7
7: def ldap_connection(options)
8: require 'active_ldap/adapter/ldap_ext'
9: Ldap.new(options)
10: end
# File lib/active_ldap/adapter/net_ldap.rb, line 9
9: def net_ldap_connection(options)
10: require 'active_ldap/adapter/net_ldap_ext'
11: NetLdap.new(options)
12: end
# File lib/active_ldap/adapter/base.rb, line 23
23: def initialize(configuration={})
24: @runtime = 0
25: @connection = nil
26: @disconnected = false
27: @entry_attributes = {}
28: @configuration = configuration.dup
29: @logger = @configuration.delete(:logger)
30: @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
31: VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
32: instance_variable_set("@#{name}", configuration[name])
33: end
34: end
# File lib/active_ldap/adapter/base.rb, line 178
178: def add(dn, entries, options={})
179: begin
180: operation(options) do
181: yield(dn, entries)
182: end
183: rescue LdapError::NoSuchObject
184: raise EntryNotFound, _("No such entry: %s") % dn
185: rescue LdapError::InvalidDnSyntax
186: raise DistinguishedNameInvalid.new(dn)
187: rescue LdapError::AlreadyExists
188: raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn]
189: rescue LdapError::StrongAuthRequired
190: raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn]
191: rescue LdapError::ObjectClassViolation
192: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
193: rescue LdapError::UnwillingToPerform
194: raise OperationNotPermitted, _("%s: %s") % [$!.message, dn]
195: end
196: end
# File lib/active_ldap/adapter/base.rb, line 62
62: def bind(options={})
63: bind_dn = options[:bind_dn] || @bind_dn
64: try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
65: if options.has_key?(:allow_anonymous)
66: allow_anonymous = options[:allow_anonymous]
67: else
68: allow_anonymous = @allow_anonymous
69: end
70: options = options.merge(:allow_anonymous => allow_anonymous)
71:
72: # Rough bind loop:
73: # Attempt 1: SASL if available
74: # Attempt 2: SIMPLE with credentials if password block
75: # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
76: if try_sasl and sasl_bind(bind_dn, options)
77: @logger.info {_('Bound to %s by SASL as %s') % [target, bind_dn]}
78: elsif simple_bind(bind_dn, options)
79: @logger.info {_('Bound to %s by simple as %s') % [target, bind_dn]}
80: elsif allow_anonymous and bind_as_anonymous(options)
81: @logger.info {_('Bound to %s as anonymous') % target}
82: else
83: message = yield if block_given?
84: message ||= _('All authentication methods for %s exhausted.') % target
85: raise AuthenticationError, message
86: end
87:
88: bound?
89: end
# File lib/active_ldap/adapter/base.rb, line 91
91: def bind_as_anonymous(options={})
92: operation(options) do
93: yield
94: end
95: end
# File lib/active_ldap/adapter/base.rb, line 41
41: def connect(options={})
42: host = options[:host] || @host
43: port = options[:port] || @port
44: method = ensure_method(options[:method] || @method)
45: @disconnected = false
46: @connection, @uri, @with_start_tls = yield(host, port, method)
47: prepare_connection(options)
48: bind(options)
49: end
# File lib/active_ldap/adapter/base.rb, line 97
97: def connecting?
98: !@connection.nil? and !@disconnected
99: end
# File lib/active_ldap/adapter/base.rb, line 163
163: def delete(targets, options={})
164: targets = [targets] unless targets.is_a?(Array)
165: return if targets.empty?
166: target = nil
167: begin
168: operation(options) do
169: targets.each do |target|
170: yield(target)
171: end
172: end
173: rescue LdapError::NoSuchObject
174: raise EntryNotFound, _("No such entry: %s") % target
175: end
176: end
# File lib/active_ldap/adapter/base.rb, line 51
51: def disconnect!(options={})
52: return if @connection.nil?
53: unbind(options)
54: @connection = @uri = @with_start_tls = nil
55: end
# File lib/active_ldap/adapter/base.rb, line 127
127: def entry_attribute(object_classes)
128: @entry_attributes[object_classes.uniq.sort] ||=
129: EntryAttribute.new(schema, object_classes)
130: end
# File lib/active_ldap/adapter/base.rb, line 216
216: def log_info(name, runtime, info=nil)
217: return unless @logger
218: return unless @logger.debug?
219: message = "LDAP: #{name} (#{'%f' % runtime})"
220: @logger.debug(format_log_entry(message, info))
221: end
# File lib/active_ldap/adapter/base.rb, line 198
198: def modify(dn, entries, options={})
199: begin
200: operation(options) do
201: yield(dn, entries)
202: end
203: rescue LdapError::UndefinedType
204: raise
205: rescue LdapError::ObjectClassViolation
206: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
207: end
208: end
# File lib/active_ldap/adapter/base.rb, line 210
210: def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
211: operation(options) do
212: yield(dn, new_rdn, delete_old_rdn, new_superior)
213: end
214: end
# File lib/active_ldap/adapter/base.rb, line 57
57: def rebind(options={})
58: unbind(options) if bound?
59: connect(options)
60: end
# File lib/active_ldap/adapter/base.rb, line 36
36: def reset_runtime
37: runtime, @runtime = @runtime, 0
38: runtime
39: end
# File lib/active_ldap/adapter/base.rb, line 101
101: def schema(options={})
102: @schema ||= operation(options) do
103: base = options[:base]
104: attrs = options[:attributes]
105:
106: attrs ||= [
107: 'objectClasses',
108: 'attributeTypes',
109: 'matchingRules',
110: 'matchingRuleUse',
111: 'dITStructureRules',
112: 'dITContentRules',
113: 'nameForms',
114: 'ldapSyntaxes',
115: #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER.
116: ]
117: base ||= root_dse_values('subschemaSubentry', options)[0]
118: base ||= 'cn=schema'
119: dn, attributes = search(:base => base,
120: :scope => :base,
121: :filter => '(objectClass=subschema)',
122: :attributes => attrs).first
123: Schema.new(attributes)
124: end
125: end
# File lib/active_ldap/adapter/base.rb, line 132
132: def search(options={})
133: filter = parse_filter(options[:filter]) || 'objectClass=*'
134: attrs = options[:attributes] || []
135: scope = ensure_scope(options[:scope] || @scope)
136: base = options[:base]
137: limit = options[:limit] || 0
138: limit = nil if limit <= 0
139:
140: attrs = attrs.to_a # just in case
141:
142: values = []
143: callback = Proc.new do |value, block|
144: value = block.call(value) if block
145: values << value
146: end
147:
148: begin
149: operation(options) do
150: yield(base, scope, filter, attrs, limit, callback)
151: end
152: rescue LdapError
153: # Do nothing on failure
154: @logger.info do
155: args = [$!.class, $!.message, filter, attrs.inspect]
156: _("Ignore error %s(%s): filter %s: attributes: %s") % args
157: end
158: end
159:
160: values
161: end
# File lib/active_ldap/adapter/base.rb, line 511
511: def assert_filter_logical_operator(operator)
512: return if operator.nil?
513: unless filter_logical_operator?(operator)
514: raise ArgumentError,
515: _("invalid logical operator: %s: available operators: %s") %
516: [operator.inspect, LOGICAL_OPERATORS.inspect]
517: end
518: end
Determine if we have exceed the retry limit or not. True is reconnecting is allowed - False if not.
# File lib/active_ldap/adapter/base.rb, line 567
567: def can_reconnect?(options={})
568: retry_limit = options[:retry_limit] || @retry_limit
569: reconnect_attempts = options[:reconnect_attempts] || 0
570:
571: retry_limit < 0 or reconnect_attempts < (retry_limit - 1)
572: end
# File lib/active_ldap/adapter/base.rb, line 490
490: def collection?(object)
491: !object.is_a?(String) and object.respond_to?(:each)
492: end
# File lib/active_ldap/adapter/base.rb, line 427
427: def construct_component(key, value, operator=nil)
428: value, options = extract_filter_value_options(value)
429: comparison_operator = options[:operator] || "="
430: if collection?(value)
431: return nil if value.empty?
432: operator, value = normalize_array_filter(value, operator)
433: values = []
434: value.each do |val|
435: if collection?(val)
436: values.concat(val.collect {|v| [key, comparison_operator, v]})
437: else
438: values << [key, comparison_operator, val]
439: end
440: end
441: values[0] = values[0][1] if filter_logical_operator?(values[0][1])
442: parse_filter(values, operator)
443: else
444: [
445: "(",
446: escape_filter_key(key),
447: comparison_operator,
448: escape_filter_value(value, options),
449: ")"
450: ].join
451: end
452: end
# File lib/active_ldap/adapter/base.rb, line 403
403: def construct_components(components, operator)
404: components.collect do |component|
405: if component.is_a?(Array)
406: if filter_logical_operator?(component[0])
407: parse_filter(component)
408: elsif component.size == 2
409: key, value = component
410: if value.is_a?(Hash)
411: parse_filter(value, key)
412: else
413: construct_component(key, value, operator)
414: end
415: else
416: construct_component(component[0], component[1..-1], operator)
417: end
418: elsif component.is_a?(Symbol)
419: assert_filter_logical_operator(component)
420: nil
421: else
422: parse_filter(component, operator)
423: end
424: end
425: end
# File lib/active_ldap/adapter/base.rb, line 475
475: def construct_filter(components, operator=nil)
476: operator = normalize_filter_logical_operator(operator)
477: components = components.compact
478: case components.size
479: when 0
480: nil
481: when 1
482: filter = components[0]
483: filter = "(!#{filter})" if operator == :not
484: filter
485: else
486: "(#{operator == :and ? '&' : '|'}#{components.join})"
487: end
488: end
# File lib/active_ldap/adapter/base.rb, line 588
588: def construct_uri(host, port, ssl)
589: protocol = ssl ? "ldaps" : "ldap"
590: URI.parse("#{protocol}://#{host}:#{port}").to_s
591: end
# File lib/active_ldap/adapter/base.rb, line 454
454: def escape_filter_key(key)
455: escape_filter_value(key.to_s)
456: end
# File lib/active_ldap/adapter/base.rb, line 458
458: def escape_filter_value(value, options={})
459: case value
460: when Numeric, DN
461: value = value.to_s
462: when Time
463: value = Schema::GeneralizedTime.new.normalize_value(value)
464: end
465: value.gsub(/(?:[()\\\0]|\*\*?)/) do |s|
466: if s == "*"
467: s
468: else
469: s = "*" if s == "**"
470: "\\%02X" % s[0]
471: end
472: end
473: end
# File lib/active_ldap/adapter/base.rb, line 384
384: def extract_filter_value_options(value)
385: options = {}
386: if value.is_a?(Array)
387: case value[0]
388: when Hash
389: options = value[0]
390: value = value[1]
391: when "=", "~=", "<=", "=>"
392: options[:operator] = value[0]
393: if value.size > 2
394: value = value[1..-1]
395: else
396: value = value[1]
397: end
398: end
399: end
400: [value, options]
401: end
# File lib/active_ldap/adapter/base.rb, line 495
495: def filter_logical_operator?(operator)
496: LOGICAL_OPERATORS.include?(operator)
497: end
# File lib/active_ldap/adapter/base.rb, line 624
624: def format_log_entry(message, info=nil)
625: if ActiveLdap::Base.colorize_logging
626: if @@row_even
627: message_color, dump_color = "4;36;1", "0;1"
628: else
629: @@row_even = true
630: message_color, dump_color = "4;35;1", "0"
631: end
632: @@row_even = !@@row_even
633:
634: log_entry = " \e[#{message_color}m#{message}\e[0m"
635: log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info
636: log_entry
637: else
638: log_entry = message
639: log_entry += ": #{info.inspect}" if info
640: log_entry
641: end
642: end
# File lib/active_ldap/adapter/base.rb, line 602
602: def log(name, info=nil)
603: if block_given?
604: if @logger and @logger.debug?
605: result = nil
606: runtime = Benchmark.realtime {result = yield}
607: @runtime += runtime
608: log_info(name, runtime, info)
609: result
610: else
611: yield
612: end
613: else
614: log_info(name, info, 0)
615: nil
616: end
617: rescue Exception
618: log_info("#{name}: FAILED", 0,
619: (info || {}).merge(:error => $!.class.name,
620: :error_message => $!.message))
621: raise
622: end
# File lib/active_ldap/adapter/base.rb, line 249
249: def need_credential_sasl_mechanism?(mechanism)
250: not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism)
251: end
# File lib/active_ldap/adapter/base.rb, line 373
373: def normalize_array_filter(filter, operator=nil)
374: filter_operator, *components = filter
375: if filter_logical_operator?(filter_operator)
376: operator = filter_operator
377: else
378: components.unshift(filter_operator)
379: components = [components] unless filter_operator.is_a?(Array)
380: end
381: [operator, components]
382: end
# File lib/active_ldap/adapter/base.rb, line 499
499: def normalize_filter_logical_operator(operator)
500: assert_filter_logical_operator(operator)
501: case (operator || :and)
502: when :and, :&
503: :and
504: when :or, :|
505: :or
506: else
507: :not
508: end
509: end
# File lib/active_ldap/adapter/base.rb, line 227
227: def operation(options)
228: retried = false
229: options = options.dup
230: options[:try_reconnect] = true unless options.has_key?(:try_reconnect)
231: try_reconnect = false
232: begin
233: reconnect_if_need(options)
234: try_reconnect = options[:try_reconnect]
235: with_timeout(try_reconnect, options) do
236: yield
237: end
238: rescue Errno::EPIPE, ConnectionError
239: if try_reconnect and !retried
240: retried = true
241: @disconnected = true
242: retry
243: else
244: raise
245: end
246: end
247: end
# File lib/active_ldap/adapter/base.rb, line 340
340: def parse_filter(filter, operator=nil)
341: return nil if filter.nil?
342: if !filter.is_a?(String) and !filter.respond_to?(:collect)
343: filter = filter.to_s
344: end
345:
346: case filter
347: when String
348: parse_filter_string(filter)
349: when Hash
350: components = filter.sort_by {|k, v| k.to_s}.collect do |key, value|
351: construct_component(key, value, operator)
352: end
353: construct_filter(components, operator)
354: else
355: operator, components = normalize_array_filter(filter, operator)
356: components = construct_components(components, operator)
357: construct_filter(components, operator)
358: end
359: end
# File lib/active_ldap/adapter/base.rb, line 361
361: def parse_filter_string(filter)
362: if /\A\s*\z/.match(filter)
363: nil
364: else
365: if filter[0, 1] == "("
366: filter
367: else
368: "(#{filter})"
369: end
370: end
371: end
# File lib/active_ldap/adapter/base.rb, line 253
253: def password(bind_dn, options={})
254: passwd = options[:password] || @password
255: return passwd if passwd
256:
257: password_block = options[:password_block] || @password_block
258: # TODO: Give a warning to reconnect users with password clearing
259: # Get the passphrase for the first time, or anew if we aren't storing
260: if password_block.respond_to?(:call)
261: passwd = password_block.call(bind_dn)
262: else
263: @logger.error {_('password_block not nil or Proc object. Ignoring.')}
264: return nil
265: end
266:
267: # Store the password for quick reference later
268: if options.has_key?(:store_password)
269: store_password = options[:store_password]
270: else
271: store_password = @store_password
272: end
273: @password = store_password ? passwd : nil
274:
275: passwd
276: end
Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.
# File lib/active_ldap/adapter/base.rb, line 522
522: def reconnect(options={})
523: options = options.dup
524: force = options[:force]
525: retry_limit = options[:retry_limit] || @retry_limit
526: retry_wait = options[:retry_wait] || @retry_wait
527: options[:reconnect_attempts] ||= 0
528:
529: loop do
530: unless can_reconnect?(options)
531: raise ConnectionError,
532: _('Giving up trying to reconnect to LDAP server.')
533: end
534:
535: @logger.debug {_('Attempting to reconnect')}
536: disconnect!
537:
538: # Reset the attempts if this was forced.
539: options[:reconnect_attempts] = 0 if force
540: options[:reconnect_attempts] += 1 if retry_limit >= 0
541: begin
542: connect(options)
543: break
544: rescue => detail
545: @logger.error do
546: _("Reconnect to server failed: %s\n" \
547: "Reconnect to server failed backtrace:\n" \
548: "%s") % [detail.exception, detail.backtrace.join("\n")]
549: end
550: # Do not loop if forced
551: raise ConnectionError, detail.message if force
552: end
553:
554: # Sleep before looping
555: sleep retry_wait
556: end
557:
558: true
559: end
# File lib/active_ldap/adapter/base.rb, line 561
561: def reconnect_if_need(options={})
562: reconnect(options) if !connecting? and can_reconnect?(options)
563: end
# File lib/active_ldap/adapter/base.rb, line 580
580: def root_dse(attrs, options={})
581: search(:base => "",
582: :scope => :base,
583: :attributes => attrs).collect do |dn, attributes|
584: attributes
585: end
586: end
# File lib/active_ldap/adapter/base.rb, line 574
574: def root_dse_values(key, options={})
575: dse = root_dse([key], options)[0]
576: return [] if dse.nil?
577: dse[key] || dse[key.downcase] || []
578: end
# File lib/active_ldap/adapter/base.rb, line 289
289: def sasl_bind(bind_dn, options={})
290: # Get all SASL mechanisms
291: mechanisms = operation(options) do
292: root_dse_values("supportedSASLMechanisms")
293: end
294:
295: if options.has_key?(:sasl_quiet)
296: sasl_quiet = options[:sasl_quiet]
297: else
298: sasl_quiet = @sasl_quiet
299: end
300:
301: sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
302: sasl_mechanisms.each do |mechanism|
303: next unless mechanisms.include?(mechanism)
304: operation(options) do
305: yield(bind_dn, mechanism, sasl_quiet)
306: return true if bound?
307: end
308: end
309: false
310: end
# File lib/active_ldap/adapter/base.rb, line 312
312: def simple_bind(bind_dn, options={})
313: return false unless bind_dn
314:
315: passwd = password(bind_dn, options)
316: return false unless passwd
317:
318: if passwd.empty?
319: if options[:allow_anonymous]
320: @logger.info {_("Skip simple bind with empty password.")}
321: return false
322: else
323: raise AuthenticationError,
324: _("Can't use empty password for simple bind.")
325: end
326: end
327:
328: begin
329: operation(options) do
330: yield(bind_dn, passwd)
331: bound?
332: end
333: rescue LdapError::InvalidDnSyntax
334: raise DistinguishedNameInvalid.new(bind_dn)
335: rescue LdapError::InvalidCredentials
336: false
337: end
338: end
# File lib/active_ldap/adapter/base.rb, line 593
593: def target
594: return nil if @uri.nil?
595: if @with_start_tls
596: "#{@uri}(StartTLS)"
597: else
598: @uri
599: end
600: end
# File lib/active_ldap/adapter/base.rb, line 278
278: def with_timeout(try_reconnect=true, options={}, &block)
279: begin
280: Timeout.alarm(@timeout, &block)
281: rescue Timeout::Error => e
282: @logger.error {_('Requested action timed out.')}
283: retry if @retry_on_timeout and try_reconnect and reconnect(options)
284: @logger.error {e.message}
285: raise TimeoutError, e.message
286: end
287: end