Class | ActiveLdap::Adapter::Base |
In: |
lib/active_ldap/adapter/base.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, :&, :|] |
# 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 16 16: def initialize(configuration={}) 17: @connection = nil 18: @disconnected = false 19: @configuration = configuration.dup 20: @logger = @configuration.delete(:logger) 21: @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS) 22: VALID_ADAPTER_CONFIGURATION_KEYS.each do |name| 23: instance_variable_set("@#{name}", configuration[name]) 24: end 25: end
# File lib/active_ldap/adapter/base.rb, line 166 166: def add(dn, entries, options={}) 167: begin 168: operation(options) do 169: yield(dn, entries) 170: end 171: rescue LdapError::NoSuchObject 172: raise EntryNotFound, _("No such entry: %s") % dn 173: rescue LdapError::InvalidDnSyntax 174: raise DistinguishedNameInvalid.new(dn) 175: rescue LdapError::AlreadyExists 176: raise EntryAlreadyExist, _("%s: %s") % [$!.message, dn] 177: rescue LdapError::StrongAuthRequired 178: raise StrongAuthenticationRequired, _("%s: %s") % [$!.message, dn] 179: rescue LdapError::ObjectClassViolation 180: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn] 181: rescue LdapError::UnwillingToPerform 182: raise OperationNotPermitted, _("%s: %s") % [$!.message, dn] 183: end 184: end
# File lib/active_ldap/adapter/base.rb, line 48 48: def bind(options={}) 49: bind_dn = options[:bind_dn] || @bind_dn 50: try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl 51: if options.has_key?(:allow_anonymous) 52: allow_anonymous = options[:allow_anonymous] 53: else 54: allow_anonymous = @allow_anonymous 55: end 56: 57: # Rough bind loop: 58: # Attempt 1: SASL if available 59: # Attempt 2: SIMPLE with credentials if password block 60: # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') 61: if try_sasl and sasl_bind(bind_dn, options) 62: @logger.info {_('Bound by SASL as %s') % bind_dn} 63: elsif simple_bind(bind_dn, options) 64: @logger.info {_('Bound by simple as %s') % bind_dn} 65: elsif allow_anonymous and bind_as_anonymous(options) 66: @logger.info {_('Bound as anonymous')} 67: else 68: message = yield if block_given? 69: message ||= _('All authentication methods exhausted.') 70: raise AuthenticationError, message 71: end 72: 73: bound? 74: end
# File lib/active_ldap/adapter/base.rb, line 76 76: def bind_as_anonymous(options={}) 77: operation(options) do 78: yield 79: end 80: end
# File lib/active_ldap/adapter/base.rb, line 27 27: def connect(options={}) 28: host = options[:host] || @host 29: port = options[:port] || @port 30: method = ensure_method(options[:method] || @method) 31: @disconnected = false 32: @connection = yield(host, port, method) 33: prepare_connection(options) 34: bind(options) 35: end
# File lib/active_ldap/adapter/base.rb, line 82 82: def connecting? 83: !@connection.nil? and !@disconnected 84: end
# File lib/active_ldap/adapter/base.rb, line 151 151: def delete(targets, options={}) 152: targets = [targets] unless targets.is_a?(Array) 153: return if targets.empty? 154: target = nil 155: begin 156: operation(options) do 157: targets.each do |target| 158: yield(target) 159: end 160: end 161: rescue LdapError::NoSuchObject 162: raise EntryNotFound, _("No such entry: %s") % target 163: end 164: end
# File lib/active_ldap/adapter/base.rb, line 37 37: def disconnect!(options={}) 38: return if @connection.nil? 39: unbind(options) 40: @connection = nil 41: end
# File lib/active_ldap/adapter/base.rb, line 112 112: def load(ldifs, options={}) 113: operation(options) do 114: ldifs.split(/(?:\r?\n){2,}/).each do |ldif| 115: yield(ldif) 116: end 117: end 118: end
# File lib/active_ldap/adapter/base.rb, line 186 186: def modify(dn, entries, options={}) 187: begin 188: operation(options) do 189: yield(dn, entries) 190: end 191: rescue LdapError::UndefinedType 192: raise 193: rescue LdapError::ObjectClassViolation 194: raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn] 195: end 196: end
# File lib/active_ldap/adapter/base.rb, line 43 43: def rebind(options={}) 44: unbind(options) if bound? 45: connect(options) 46: end
# File lib/active_ldap/adapter/base.rb, line 86 86: def schema(options={}) 87: @schema ||= operation(options) do 88: base = options[:base] 89: attrs = options[:attributes] 90: 91: attrs ||= [ 92: 'objectClasses', 93: 'attributeTypes', 94: 'matchingRules', 95: 'matchingRuleUse', 96: 'dITStructureRules', 97: 'dITContentRules', 98: 'nameForms', 99: 'ldapSyntaxes', 100: #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER. 101: ] 102: base ||= root_dse_values('subschemaSubentry', options)[0] 103: base ||= 'cn=schema' 104: dn, attributes = search(:base => base, 105: :scope => :base, 106: :filter => '(objectClass=subschema)', 107: :attributes => attrs).first 108: Schema.new(attributes) 109: end 110: end
# File lib/active_ldap/adapter/base.rb, line 120 120: def search(options={}) 121: filter = parse_filter(options[:filter]) || 'objectClass=*' 122: attrs = options[:attributes] || [] 123: scope = ensure_scope(options[:scope] || @scope) 124: base = options[:base] 125: limit = options[:limit] || 0 126: limit = nil if limit <= 0 127: 128: attrs = attrs.to_a # just in case 129: 130: values = [] 131: callback = Proc.new do |value, block| 132: value = block.call(value) if block 133: values << value 134: end 135: 136: begin 137: operation(options) do 138: yield(base, scope, filter, attrs, limit, callback) 139: end 140: rescue LdapError 141: # Do nothing on failure 142: @logger.info do 143: args = [$!.class, $!.message, filter, attrs.inspect] 144: _("Ignore error %s(%s): filter %s: attributes: %s") % args 145: end 146: end 147: 148: values 149: end
# File lib/active_ldap/adapter/base.rb, line 461 461: def assert_filter_logical_operator(operator) 462: return if operator.nil? 463: unless filter_logical_operator?(operator) 464: raise ArgumentError, 465: _("invalid logical operator: %s: available operators: %s") % 466: [operator.inspect, LOGICAL_OPERATORS.inspect] 467: end 468: 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 517 517: def can_reconnect?(options={}) 518: retry_limit = options[:retry_limit] || @retry_limit 519: reconnect_attempts = options[:reconnect_attempts] || 0 520: 521: retry_limit < 0 or reconnect_attempts < (retry_limit - 1) 522: end
# File lib/active_ldap/adapter/base.rb, line 440 440: def collection?(object) 441: !object.is_a?(String) and object.respond_to?(:each) 442: end
# File lib/active_ldap/adapter/base.rb, line 380 380: def construct_component(key, value, operator=nil) 381: value, options = extract_filter_value_options(value) 382: if collection?(value) 383: values = [] 384: value.each do |val| 385: if collection?(val) 386: values.concat(val.collect {|v| [key, v]}) 387: else 388: values << [key, val] 389: end 390: end 391: values[0] = values[0][1] if filter_logical_operator?(values[0][1]) 392: parse_filter(values, operator) 393: else 394: [ 395: "(", 396: escape_filter_key(key), 397: options[:operator] || "=", 398: escape_filter_value(value, options), 399: ")" 400: ].join 401: end 402: end
# File lib/active_ldap/adapter/base.rb, line 425 425: def construct_filter(components, operator=nil) 426: operator = normalize_filter_logical_operator(operator) 427: components = components.compact 428: case components.size 429: when 0 430: nil 431: when 1 432: filter = components[0] 433: filter = "(!#{filter})" if operator == :not 434: filter 435: else 436: "(#{operator == :and ? '&' : '|'}#{components.join})" 437: end 438: end
# File lib/active_ldap/adapter/base.rb, line 404 404: def escape_filter_key(key) 405: escape_filter_value(key.to_s) 406: end
# File lib/active_ldap/adapter/base.rb, line 408 408: def escape_filter_value(value, options={}) 409: case value 410: when Numeric, DN 411: value = value.to_s 412: when Time 413: value = Schema::GeneralizedTime.new.normalize_value(value) 414: end 415: value.gsub(/(?:[()\\\0]|\*\*?)/) do |s| 416: if s == "*" 417: s 418: else 419: s = "*" if s == "**" 420: "\\%02X" % s[0] 421: end 422: end 423: end
# File lib/active_ldap/adapter/base.rb, line 365 365: def extract_filter_value_options(value) 366: options = {} 367: if value.is_a?(Array) 368: case value[0] 369: when Hash 370: options = value[0] 371: value = value[1] 372: when "=", "~=", "<=", "=>" 373: options[:operator] = value[1] 374: value = value[1] 375: end 376: end 377: [value, options] 378: end
# File lib/active_ldap/adapter/base.rb, line 445 445: def filter_logical_operator?(operator) 446: LOGICAL_OPERATORS.include?(operator) 447: end
# File lib/active_ldap/adapter/base.rb, line 222 222: def need_credential_sasl_mechanism?(mechanism) 223: not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism) 224: end
# File lib/active_ldap/adapter/base.rb, line 355 355: def normalize_array_filter(filter, operator=nil) 356: filter_operator, *components = filter 357: if filter_logical_operator?(filter_operator) 358: operator = filter_operator 359: else 360: components.unshift(filter_operator) 361: end 362: [operator, components] 363: end
# File lib/active_ldap/adapter/base.rb, line 449 449: def normalize_filter_logical_operator(operator) 450: assert_filter_logical_operator(operator) 451: case (operator || :and) 452: when :and, :& 453: :and 454: when :or, :| 455: :or 456: else 457: :not 458: end 459: end
# File lib/active_ldap/adapter/base.rb, line 202 202: def operation(options) 203: retried = false 204: begin 205: reconnect_if_need 206: try_reconnect = !options.has_key?(:try_reconnect) || 207: options[:try_reconnect] 208: with_timeout(try_reconnect, options) do 209: yield 210: end 211: rescue Errno::EPIPE 212: if retried or !try_reconnect 213: raise 214: else 215: retried = true 216: @disconnected = true 217: retry 218: end 219: end 220: end
# File lib/active_ldap/adapter/base.rb, line 305 305: def parse_filter(filter, operator=nil) 306: return nil if filter.nil? 307: if !filter.is_a?(String) and !filter.respond_to?(:collect) 308: filter = filter.to_s 309: end 310: 311: case filter 312: when String 313: parse_filter_string(filter) 314: when Hash 315: components = filter.sort_by {|k, v| k.to_s}.collect do |key, value| 316: construct_component(key, value, operator) 317: end 318: construct_filter(components, operator) 319: else 320: operator, components = normalize_array_filter(filter, operator) 321: 322: components = components.collect do |component| 323: if component.is_a?(Array) and component.size == 2 324: key, value = component 325: if filter_logical_operator?(key) 326: parse_filter(component) 327: elsif value.is_a?(Hash) 328: parse_filter(value, key) 329: else 330: construct_component(key, value, operator) 331: end 332: elsif component.is_a?(Symbol) 333: assert_filter_logical_operator(component) 334: nil 335: else 336: parse_filter(component, operator) 337: end 338: end 339: construct_filter(components, operator) 340: end 341: end
# File lib/active_ldap/adapter/base.rb, line 343 343: def parse_filter_string(filter) 344: if /\A\s*\z/.match(filter) 345: nil 346: else 347: if filter[0, 1] == "(" 348: filter 349: else 350: "(#{filter})" 351: end 352: end 353: end
# File lib/active_ldap/adapter/base.rb, line 226 226: def password(bind_dn, options={}) 227: passwd = options[:password] || @password 228: return passwd if passwd 229: 230: password_block = options[:password_block] || @password_block 231: # TODO: Give a warning to reconnect users with password clearing 232: # Get the passphrase for the first time, or anew if we aren't storing 233: if password_block.respond_to?(:call) 234: passwd = password_block.call(bind_dn) 235: else 236: @logger.error {_('password_block not nil or Proc object. Ignoring.')} 237: return nil 238: end 239: 240: # Store the password for quick reference later 241: if options.has_key?(:store_password) 242: store_password = options[:store_password] 243: else 244: store_password = @store_password 245: end 246: @password = store_password ? passwd : nil 247: 248: passwd 249: 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 472 472: def reconnect(options={}) 473: options = options.dup 474: force = options[:force] 475: retry_limit = options[:retry_limit] || @retry_limit 476: retry_wait = options[:retry_wait] || @retry_wait 477: options[:reconnect_attempts] ||= 0 478: 479: loop do 480: unless can_reconnect?(options) 481: raise ConnectionError, 482: _('Giving up trying to reconnect to LDAP server.') 483: end 484: 485: @logger.debug {_('Attempting to reconnect')} 486: disconnect! 487: 488: # Reset the attempts if this was forced. 489: options[:reconnect_attempts] = 0 if force 490: options[:reconnect_attempts] += 1 if retry_limit >= 0 491: begin 492: connect(options) 493: break 494: rescue => detail 495: @logger.error do 496: _("Reconnect to server failed: %s\n" \ 497: "Reconnect to server failed backtrace:\n" \ 498: "%s") % [detail.exception, detail.backtrace.join("\n")] 499: end 500: # Do not loop if forced 501: raise ConnectionError, detail.message if force 502: end 503: 504: # Sleep before looping 505: sleep retry_wait 506: end 507: 508: true 509: end
# File lib/active_ldap/adapter/base.rb, line 511 511: def reconnect_if_need(options={}) 512: reconnect(options) if !connecting? and can_reconnect?(options) 513: end
# File lib/active_ldap/adapter/base.rb, line 524 524: def root_dse_values(key, options={}) 525: dse = root_dse([key], options)[0] 526: return [] if dse.nil? 527: dse[key] || dse[key.downcase] || [] 528: end
# File lib/active_ldap/adapter/base.rb, line 262 262: def sasl_bind(bind_dn, options={}) 263: return false unless bind_dn 264: 265: # Get all SASL mechanisms 266: mechanisms = operation(options) do 267: root_dse_values("supportedSASLMechanisms") 268: end 269: 270: if options.has_key?(:sasl_quiet) 271: sasl_quiet = options[:sasl_quiet] 272: else 273: sasl_quiet = @sasl_quiet 274: end 275: 276: sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms 277: sasl_mechanisms.each do |mechanism| 278: next unless mechanisms.include?(mechanism) 279: operation(options) do 280: yield(bind_dn, mechanism, sasl_quiet) 281: return true if bound? 282: end 283: end 284: false 285: end
# File lib/active_ldap/adapter/base.rb, line 287 287: def simple_bind(bind_dn, options={}) 288: return false unless bind_dn 289: 290: passwd = password(bind_dn, options) 291: return false unless passwd 292: 293: begin 294: operation(options) do 295: yield(bind_dn, passwd) 296: bound? 297: end 298: rescue LdapError::InvalidDnSyntax 299: raise DistinguishedNameInvalid.new(bind_dn) 300: rescue LdapError::InvalidCredentials 301: false 302: end 303: end
# File lib/active_ldap/adapter/base.rb, line 251 251: def with_timeout(try_reconnect=true, options={}, &block) 252: begin 253: Timeout.alarm(@timeout, &block) 254: rescue Timeout::Error => e 255: @logger.error {_('Requested action timed out.')} 256: retry if try_reconnect and @retry_on_timeout and reconnect(options) 257: @logger.error {e.message} 258: raise TimeoutError, e.message 259: end 260: end