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: @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 196 196: def add(dn, entries, options={}) 197: begin 198: operation(options) do 199: yield(dn, entries) 200: end 201: rescue LdapError::NoSuchObject 202: raise EntryNotFound, _("No such entry: %s") % dn 203: rescue LdapError::InvalidDnSyntax 204: raise DistinguishedNameInvalid.new(dn) 205: rescue LdapError::AlreadyExists 206: raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn] 207: rescue LdapError::StrongAuthRequired 208: raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn] 209: rescue LdapError::ObjectClassViolation 210: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn] 211: rescue LdapError::UnwillingToPerform 212: raise OperationNotPermitted, _("%s: %s") % [$!.message, dn] 213: end 214: end
# File lib/active_ldap/adapter/base.rb, line 67 67: def bind(options={}) 68: @bind_tried = true 69: 70: bind_dn = 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: begin 185: yield(target) 186: rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess 187: raise OperationNotPermitted, _("%s: %s") % [$!.message, target] 188: end 189: end 190: end 191: rescue LdapError::NoSuchObject 192: raise EntryNotFound, _("No such entry: %s") % target 193: end 194: 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 142 142: def entry_attribute(object_classes) 143: @entry_attributes[object_classes.uniq.sort] ||= 144: EntryAttribute.new(schema, object_classes) 145: end
# File lib/active_ldap/adapter/base.rb, line 238 238: def log_info(name, runtime_in_seconds, info=nil) 239: return unless @logger 240: return unless @logger.debug? 241: message = "LDAP: #{name} (#{'%.1f' % (runtime_in_seconds * 1000)}ms)" 242: @logger.debug(format_log_entry(message, info)) 243: end
# File lib/active_ldap/adapter/base.rb, line 216 216: def modify(dn, entries, options={}) 217: begin 218: operation(options) do 219: begin 220: yield(dn, entries) 221: rescue LdapError::UnwillingToPerform, LdapError::InsufficientAccess 222: raise OperationNotPermitted, _("%s: %s") % [$!.message, target] 223: end 224: end 225: rescue LdapError::UndefinedType 226: raise 227: rescue LdapError::ObjectClassViolation 228: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn] 229: end 230: end
# File lib/active_ldap/adapter/base.rb, line 232 232: def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={}) 233: operation(options) do 234: yield(dn, new_rdn, delete_old_rdn, new_superior) 235: end 236: 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: dn, attributes = search(:base => base, 135: :scope => :base, 136: :filter => '(objectClass=subschema)', 137: :attributes => attrs).first 138: Schema.new(attributes) 139: end 140: end
# File lib/active_ldap/adapter/base.rb, line 147 147: def search(options={}) 148: filter = parse_filter(options[:filter]) || 'objectClass=*' 149: attrs = options[:attributes] || [] 150: scope = ensure_scope(options[:scope] || @scope) 151: base = options[:base] 152: limit = options[:limit] || 0 153: limit = nil if limit <= 0 154: 155: attrs = attrs.to_a # just in case 156: 157: values = [] 158: callback = Proc.new do |value, block| 159: value = block.call(value) if block 160: values << value 161: end 162: 163: begin 164: operation(options) do 165: yield(base, scope, filter, attrs, limit, callback) 166: end 167: rescue LdapError 168: # Do nothing on failure 169: @logger.info do 170: args = [$!.class, $!.message, filter, attrs.inspect] 171: _("Ignore error %s(%s): filter %s: attributes: %s") % args 172: end 173: end 174: 175: values 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 547 547: def assert_filter_logical_operator(operator) 548: return if operator.nil? 549: unless filter_logical_operator?(operator) 550: raise ArgumentError, 551: _("invalid logical operator: %s: available operators: %s") % 552: [operator.inspect, LOGICAL_OPERATORS.inspect] 553: end 554: 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 608 608: def can_reconnect?(options={}) 609: retry_limit = options[:retry_limit] || @retry_limit 610: reconnect_attempts = options[:reconnect_attempts] || 0 611: 612: retry_limit < 0 or reconnect_attempts <= retry_limit 613: end
# File lib/active_ldap/adapter/base.rb, line 526 526: def collection?(object) 527: !object.is_a?(String) and object.respond_to?(:each) 528: end
# File lib/active_ldap/adapter/base.rb, line 459 459: def construct_component(key, value, operator=nil) 460: value, options = extract_filter_value_options(value) 461: comparison_operator = options[:operator] || "=" 462: if collection?(value) 463: return nil if value.empty? 464: operator, value = normalize_array_filter(value, operator) 465: values = [] 466: value.each do |val| 467: if collection?(val) 468: values.concat(val.collect {|v| [key, comparison_operator, v]}) 469: else 470: values << [key, comparison_operator, val] 471: end 472: end 473: values[0] = values[0][1] if filter_logical_operator?(values[0][1]) 474: parse_filter(values, operator) 475: else 476: [ 477: "(", 478: escape_filter_key(key), 479: comparison_operator, 480: escape_filter_value(value, options), 481: ")" 482: ].join 483: end 484: end
# File lib/active_ldap/adapter/base.rb, line 435 435: def construct_components(components, operator) 436: components.collect do |component| 437: if component.is_a?(Array) 438: if filter_logical_operator?(component[0]) 439: parse_filter(component) 440: elsif component.size == 2 441: key, value = component 442: if value.is_a?(Hash) 443: parse_filter(value, key) 444: else 445: construct_component(key, value, operator) 446: end 447: else 448: construct_component(component[0], component[1..-1], operator) 449: end 450: elsif component.is_a?(Symbol) 451: assert_filter_logical_operator(component) 452: nil 453: else 454: parse_filter(component, operator) 455: end 456: end 457: end
# File lib/active_ldap/adapter/base.rb, line 511 511: def construct_filter(components, operator=nil) 512: operator = normalize_filter_logical_operator(operator) 513: components = components.compact 514: case components.size 515: when 0 516: nil 517: when 1 518: filter = components[0] 519: filter = "(!#{filter})" if operator == :not 520: filter 521: else 522: "(#{operator == :and ? '&' : '|'}#{components.join})" 523: end 524: end
# File lib/active_ldap/adapter/base.rb, line 629 629: def construct_uri(host, port, ssl) 630: protocol = ssl ? "ldaps" : "ldap" 631: URI.parse("#{protocol}://#{host}:#{port}").to_s 632: end
# File lib/active_ldap/adapter/base.rb, line 246 246: def ensure_port(method) 247: if method == :ssl 248: URI::LDAPS::DEFAULT_PORT 249: else 250: URI::LDAP::DEFAULT_PORT 251: end 252: end
# File lib/active_ldap/adapter/base.rb, line 486 486: def escape_filter_key(key) 487: escape_filter_value(key.to_s) 488: end
# File lib/active_ldap/adapter/base.rb, line 490 490: def escape_filter_value(value, options={}) 491: case value 492: when Numeric, DN 493: value = value.to_s 494: when Time 495: value = Schema::GeneralizedTime.new.normalize_value(value) 496: end 497: value.gsub(/(?:[()\\\0]|\*\*?)/) do |s| 498: if s == "*" 499: s 500: else 501: s = "*" if s == "**" 502: if s.respond_to?(:getbyte) 503: "\\%02X" % s.getbyte(0) 504: else 505: "\\%02X" % s[0] 506: end 507: end 508: end 509: end
# File lib/active_ldap/adapter/base.rb, line 416 416: def extract_filter_value_options(value) 417: options = {} 418: if value.is_a?(Array) 419: case value[0] 420: when Hash 421: options = value[0] 422: value = value[1] 423: when "=", "~=", "<=", ">=" 424: options[:operator] = value[0] 425: if value.size > 2 426: value = value[1..-1] 427: else 428: value = value[1] 429: end 430: end 431: end 432: [value, options] 433: end
# File lib/active_ldap/adapter/base.rb, line 531 531: def filter_logical_operator?(operator) 532: LOGICAL_OPERATORS.include?(operator) 533: end
# File lib/active_ldap/adapter/base.rb, line 661 661: def format_log_entry(message, info=nil) 662: if ActiveLdap::Base.colorize_logging 663: if @@row_even 664: message_color, dump_color = "4;36;1", "0;1" 665: else 666: @@row_even = true 667: message_color, dump_color = "4;35;1", "0" 668: end 669: @@row_even = !@@row_even 670: 671: log_entry = " \e[#{message_color}m#{message}\e[0m" 672: log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info 673: log_entry 674: else 675: log_entry = message 676: log_entry += ": #{info.inspect}" if info 677: log_entry 678: end 679: end
# File lib/active_ldap/adapter/base.rb, line 643 643: def log(name, info=nil) 644: if block_given? 645: result = nil 646: seconds = Benchmark.realtime {result = yield} 647: @runtime += seconds 648: log_info(name, seconds, info) 649: result 650: else 651: log_info(name, 0, info) 652: nil 653: end 654: rescue Exception 655: log_info("#{name}: FAILED", 0, 656: (info || {}).merge(:error => $!.class.name, 657: :error_message => $!.message)) 658: raise 659: end
# File lib/active_ldap/adapter/base.rb, line 279 279: def need_credential_sasl_mechanism?(mechanism) 280: not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism) 281: end
# File lib/active_ldap/adapter/base.rb, line 405 405: def normalize_array_filter(filter, operator=nil) 406: filter_operator, *components = filter 407: if filter_logical_operator?(filter_operator) 408: operator = filter_operator 409: else 410: components.unshift(filter_operator) 411: components = [components] unless filter_operator.is_a?(Array) 412: end 413: [operator, components] 414: end
# File lib/active_ldap/adapter/base.rb, line 535 535: def normalize_filter_logical_operator(operator) 536: assert_filter_logical_operator(operator) 537: case (operator || :and) 538: when :and, :& 539: :and 540: when :or, :| 541: :or 542: else 543: :not 544: end 545: end
# File lib/active_ldap/adapter/base.rb, line 257 257: def operation(options) 258: retried = false 259: options = options.dup 260: options[:try_reconnect] = true unless options.has_key?(:try_reconnect) 261: try_reconnect = false 262: begin 263: reconnect_if_need(options) 264: try_reconnect = options[:try_reconnect] 265: with_timeout(try_reconnect, options) do 266: yield 267: end 268: rescue Errno::EPIPE, ConnectionError 269: if try_reconnect and !retried 270: retried = true 271: @disconnected = true 272: retry 273: else 274: raise 275: end 276: end 277: end
# File lib/active_ldap/adapter/base.rb, line 372 372: def parse_filter(filter, operator=nil) 373: return nil if filter.nil? 374: if !filter.is_a?(String) and !filter.respond_to?(:collect) 375: filter = filter.to_s 376: end 377: 378: case filter 379: when String 380: parse_filter_string(filter) 381: when Hash 382: components = filter.sort_by {|k, v| k.to_s}.collect do |key, value| 383: construct_component(key, value, operator) 384: end 385: construct_filter(components, operator) 386: else 387: operator, components = normalize_array_filter(filter, operator) 388: components = construct_components(components, operator) 389: construct_filter(components, operator) 390: end 391: end
# File lib/active_ldap/adapter/base.rb, line 393 393: def parse_filter_string(filter) 394: if /\A\s*\z/.match(filter) 395: nil 396: else 397: if filter[0, 1] == "(" 398: filter 399: else 400: "(#{filter})" 401: end 402: end 403: end
# File lib/active_ldap/adapter/base.rb, line 283 283: def password(bind_dn, options={}) 284: passwd = options[:password] || @password 285: return passwd if passwd 286: 287: password_block = options[:password_block] || @password_block 288: # TODO: Give a warning to reconnect users with password clearing 289: # Get the passphrase for the first time, or anew if we aren't storing 290: if password_block.respond_to?(:call) 291: passwd = password_block.call(bind_dn) 292: else 293: @logger.error {_('password_block not nil or Proc object. Ignoring.')} 294: return nil 295: end 296: 297: # Store the password for quick reference later 298: if options.has_key?(:store_password) 299: store_password = options[:store_password] 300: else 301: store_password = @store_password 302: end 303: @password = store_password ? passwd : nil 304: 305: passwd 306: 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 558 558: def reconnect(options={}) 559: options = options.dup 560: force = options[:force] 561: retry_limit = options[:retry_limit] || @retry_limit 562: retry_wait = options[:retry_wait] || @retry_wait 563: options[:reconnect_attempts] ||= 0 564: 565: loop do 566: @logger.debug {_('Attempting to reconnect')} 567: disconnect! 568: 569: # Reset the attempts if this was forced. 570: options[:reconnect_attempts] = 0 if force 571: options[:reconnect_attempts] += 1 if retry_limit >= 0 572: begin 573: connect(options) 574: break 575: rescue AuthenticationError 576: raise 577: rescue => detail 578: @logger.error do 579: _("Reconnect to server failed: %s\n" \ 580: "Reconnect to server failed backtrace:\n" \ 581: "%s") % [detail.exception, detail.backtrace.join("\n")] 582: end 583: # Do not loop if forced 584: raise ConnectionError, detail.message if force 585: end 586: 587: unless can_reconnect?(options) 588: raise ConnectionError, 589: _('Giving up trying to reconnect to LDAP server.') 590: end 591: 592: # Sleep before looping 593: sleep retry_wait 594: end 595: 596: true 597: end
# File lib/active_ldap/adapter/base.rb, line 599 599: def reconnect_if_need(options={}) 600: return if connecting? 601: with_timeout(false, options) do 602: reconnect(options) 603: end 604: end
# File lib/active_ldap/adapter/base.rb, line 621 621: def root_dse(attrs, options={}) 622: search(:base => "", 623: :scope => :base, 624: :attributes => attrs).collect do |dn, attributes| 625: attributes 626: end 627: end
# File lib/active_ldap/adapter/base.rb, line 615 615: def root_dse_values(key, options={}) 616: dse = root_dse([key], options)[0] 617: return [] if dse.nil? 618: dse[key] || dse[key.downcase] || [] 619: end
# File lib/active_ldap/adapter/base.rb, line 327 327: def sasl_bind(bind_dn, options={}) 328: # Get all SASL mechanisms 329: mechanisms = operation(options) do 330: root_dse_values("supportedSASLMechanisms") 331: end 332: 333: if options.has_key?(:sasl_quiet) 334: sasl_quiet = options[:sasl_quiet] 335: else 336: sasl_quiet = @sasl_quiet 337: end 338: 339: sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms 340: sasl_mechanisms.each do |mechanism| 341: next unless mechanisms.include?(mechanism) 342: return true if yield(bind_dn, mechanism, sasl_quiet) 343: end 344: false 345: end
# File lib/active_ldap/adapter/base.rb, line 347 347: def simple_bind(bind_dn, options={}) 348: return false unless bind_dn 349: 350: passwd = password(bind_dn, options) 351: return false unless passwd 352: 353: if passwd.empty? 354: if options[:allow_anonymous] 355: @logger.info {_("Skip simple bind with empty password.")} 356: return false 357: else 358: raise AuthenticationError, 359: _("Can't use empty password for simple bind.") 360: end 361: end 362: 363: begin 364: yield(bind_dn, passwd) 365: rescue LdapError::InvalidDnSyntax 366: raise DistinguishedNameInvalid.new(bind_dn) 367: rescue LdapError::InvalidCredentials 368: false 369: end 370: end
# File lib/active_ldap/adapter/base.rb, line 634 634: def target 635: return nil if @uri.nil? 636: if @with_start_tls 637: "#{@uri}(StartTLS)" 638: else 639: @uri 640: end 641: end
# File lib/active_ldap/adapter/base.rb, line 308 308: def with_timeout(try_reconnect=true, options={}, &block) 309: n_retries = 0 310: retry_limit = options[:retry_limit] || @retry_limit 311: begin 312: Timeout.alarm(@timeout, &block) 313: rescue Timeout::Error => e 314: @logger.error {_('Requested action timed out.')} 315: if @retry_on_timeout and retry_limit < 0 and n_retries <= retry_limit 316: if connecting? 317: retry 318: elsif try_reconnect 319: retry if with_timeout(false, options) {reconnect(options)} 320: end 321: end 322: @logger.error {e.message} 323: raise TimeoutError, e.message 324: end 325: end