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