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] |
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