lib/active_ldap/adapter/base.rb in activeldap-0.9.0 vs lib/active_ldap/adapter/base.rb in activeldap-0.10.0
- old
+ new
@@ -1,6 +1,9 @@
+require 'benchmark'
+
require 'active_ldap/schema'
+require 'active_ldap/entry_attribute'
require 'active_ldap/ldap_error'
module ActiveLdap
module Adapter
class Base
@@ -11,35 +14,46 @@
:retry_wait, :bind_dn, :password,
:password_block, :try_sasl,
:sasl_mechanisms, :sasl_quiet,
:allow_anonymous, :store_password,
:scope]
+
+ @@row_even = true
+
+ attr_reader :runtime
def initialize(configuration={})
+ @runtime = 0
@connection = nil
@disconnected = false
+ @entry_attributes = {}
@configuration = configuration.dup
@logger = @configuration.delete(:logger)
@configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
instance_variable_set("@#{name}", configuration[name])
end
end
+ def reset_runtime
+ runtime, @runtime = @runtime, 0
+ runtime
+ end
+
def connect(options={})
host = options[:host] || @host
port = options[:port] || @port
method = ensure_method(options[:method] || @method)
@disconnected = false
- @connection = yield(host, port, method)
+ @connection, @uri, @with_start_tls = yield(host, port, method)
prepare_connection(options)
bind(options)
end
def disconnect!(options={})
return if @connection.nil?
unbind(options)
- @connection = nil
+ @connection = @uri = @with_start_tls = nil
end
def rebind(options={})
unbind(options) if bound?
connect(options)
@@ -51,24 +65,25 @@
if options.has_key?(:allow_anonymous)
allow_anonymous = options[:allow_anonymous]
else
allow_anonymous = @allow_anonymous
end
+ options = options.merge(:allow_anonymous => allow_anonymous)
# Rough bind loop:
# Attempt 1: SASL if available
# Attempt 2: SIMPLE with credentials if password block
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
if try_sasl and sasl_bind(bind_dn, options)
- @logger.info {_('Bound by SASL as %s') % bind_dn}
+ @logger.info {_('Bound to %s by SASL as %s') % [target, bind_dn]}
elsif simple_bind(bind_dn, options)
- @logger.info {_('Bound by simple as %s') % bind_dn}
+ @logger.info {_('Bound to %s by simple as %s') % [target, bind_dn]}
elsif allow_anonymous and bind_as_anonymous(options)
- @logger.info {_('Bound as anonymous')}
+ @logger.info {_('Bound to %s as anonymous') % target}
else
message = yield if block_given?
- message ||= _('All authentication methods exhausted.')
+ message ||= _('All authentication methods for %s exhausted.') % target
raise AuthenticationError, message
end
bound?
end
@@ -107,16 +122,13 @@
:attributes => attrs).first
Schema.new(attributes)
end
end
- def load(ldifs, options={})
- operation(options) do
- ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
- yield(ldif)
- end
- end
+ def entry_attribute(object_classes)
+ @entry_attributes[object_classes.uniq.sort] ||=
+ EntryAttribute.new(schema, object_classes)
end
def search(options={})
filter = parse_filter(options[:filter]) || 'objectClass=*'
attrs = options[:attributes] || []
@@ -193,30 +205,45 @@
rescue LdapError::ObjectClassViolation
raise RequiredAttributeMissed, _("%s: %s") % [$!.message, dn]
end
end
+ def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
+ operation(options) do
+ yield(dn, new_rdn, delete_old_rdn, new_superior)
+ end
+ end
+
+ def log_info(name, runtime, info=nil)
+ return unless @logger
+ return unless @logger.debug?
+ message = "LDAP: #{name} (#{'%f' % runtime})"
+ @logger.debug(format_log_entry(message, info))
+ end
+
private
def prepare_connection(options)
end
def operation(options)
retried = false
+ options = options.dup
+ options[:try_reconnect] = true unless options.has_key?(:try_reconnect)
+ try_reconnect = false
begin
- reconnect_if_need
- try_reconnect = !options.has_key?(:try_reconnect) ||
- options[:try_reconnect]
+ reconnect_if_need(options)
+ try_reconnect = options[:try_reconnect]
with_timeout(try_reconnect, options) do
yield
end
- rescue Errno::EPIPE
- if retried or !try_reconnect
- raise
- else
+ rescue Errno::EPIPE, ConnectionError
+ if try_reconnect and !retried
retried = true
@disconnected = true
retry
+ else
+ raise
end
end
end
def need_credential_sasl_mechanism?(mechanism)
@@ -251,11 +278,11 @@
def with_timeout(try_reconnect=true, options={}, &block)
begin
Timeout.alarm(@timeout, &block)
rescue Timeout::Error => e
@logger.error {_('Requested action timed out.')}
- retry if try_reconnect and @retry_on_timeout and reconnect(options)
+ retry if @retry_on_timeout and try_reconnect and reconnect(options)
@logger.error {e.message}
raise TimeoutError, e.message
end
end
@@ -288,10 +315,20 @@
return false unless bind_dn
passwd = password(bind_dn, options)
return false unless passwd
+ if passwd.empty?
+ if options[:allow_anonymous]
+ @logger.info {_("Skip simple bind with empty password.")}
+ return false
+ else
+ raise AuthenticationError,
+ _("Can't use empty password for simple bind.")
+ end
+ end
+
begin
operation(options) do
yield(bind_dn, passwd)
bound?
end
@@ -316,28 +353,11 @@
construct_component(key, value, operator)
end
construct_filter(components, operator)
else
operator, components = normalize_array_filter(filter, operator)
-
- components = components.collect do |component|
- if component.is_a?(Array) and component.size == 2
- key, value = component
- if filter_logical_operator?(key)
- parse_filter(component)
- elsif value.is_a?(Hash)
- parse_filter(value, key)
- else
- construct_component(key, value, operator)
- end
- elsif component.is_a?(Symbol)
- assert_filter_logical_operator(component)
- nil
- else
- parse_filter(component, operator)
- end
- end
+ components = construct_components(components, operator)
construct_filter(components, operator)
end
end
def parse_filter_string(filter)
@@ -356,10 +376,11 @@
filter_operator, *components = filter
if filter_logical_operator?(filter_operator)
operator = filter_operator
else
components.unshift(filter_operator)
+ components = [components] unless filter_operator.is_a?(Array)
end
[operator, components]
end
def extract_filter_value_options(value)
@@ -368,35 +389,66 @@
case value[0]
when Hash
options = value[0]
value = value[1]
when "=", "~=", "<=", "=>"
- options[:operator] = value[1]
- value = value[1]
+ options[:operator] = value[0]
+ if value.size > 2
+ value = value[1..-1]
+ else
+ value = value[1]
+ end
end
end
[value, options]
end
+ def construct_components(components, operator)
+ components.collect do |component|
+ if component.is_a?(Array)
+ if filter_logical_operator?(component[0])
+ parse_filter(component)
+ elsif component.size == 2
+ key, value = component
+ if value.is_a?(Hash)
+ parse_filter(value, key)
+ else
+ construct_component(key, value, operator)
+ end
+ else
+ construct_component(component[0], component[1..-1], operator)
+ end
+ elsif component.is_a?(Symbol)
+ assert_filter_logical_operator(component)
+ nil
+ else
+ parse_filter(component, operator)
+ end
+ end
+ end
+
def construct_component(key, value, operator=nil)
value, options = extract_filter_value_options(value)
+ comparison_operator = options[:operator] || "="
if collection?(value)
+ return nil if value.empty?
+ operator, value = normalize_array_filter(value, operator)
values = []
value.each do |val|
if collection?(val)
- values.concat(val.collect {|v| [key, v]})
+ values.concat(val.collect {|v| [key, comparison_operator, v]})
else
- values << [key, val]
+ values << [key, comparison_operator, val]
end
end
values[0] = values[0][1] if filter_logical_operator?(values[0][1])
parse_filter(values, operator)
else
[
"(",
escape_filter_key(key),
- options[:operator] || "=",
+ comparison_operator,
escape_filter_value(value, options),
")"
].join
end
end
@@ -523,9 +575,73 @@
def root_dse_values(key, options={})
dse = root_dse([key], options)[0]
return [] if dse.nil?
dse[key] || dse[key.downcase] || []
+ end
+
+ def root_dse(attrs, options={})
+ search(:base => "",
+ :scope => :base,
+ :attributes => attrs).collect do |dn, attributes|
+ attributes
+ end
+ end
+
+ def construct_uri(host, port, ssl)
+ protocol = ssl ? "ldaps" : "ldap"
+ URI.parse("#{protocol}://#{host}:#{port}").to_s
+ end
+
+ def target
+ return nil if @uri.nil?
+ if @with_start_tls
+ "#{@uri}(StartTLS)"
+ else
+ @uri
+ end
+ end
+
+ def log(name, info=nil)
+ if block_given?
+ if @logger and @logger.debug?
+ result = nil
+ runtime = Benchmark.realtime {result = yield}
+ @runtime += runtime
+ log_info(name, runtime, info)
+ result
+ else
+ yield
+ end
+ else
+ log_info(name, info, 0)
+ nil
+ end
+ rescue Exception
+ log_info("#{name}: FAILED", 0,
+ (info || {}).merge(:error => $!.class.name,
+ :error_message => $!.message))
+ raise
+ end
+
+ def format_log_entry(message, info=nil)
+ if ActiveLdap::Base.colorize_logging
+ if @@row_even
+ message_color, dump_color = "4;36;1", "0;1"
+ else
+ @@row_even = true
+ message_color, dump_color = "4;35;1", "0"
+ end
+ @@row_even = !@@row_even
+
+ log_entry = " \e[#{message_color}m#{message}\e[0m"
+ log_entry << ": \e[#{dump_color}m#{info.inspect}\e[0m" if info
+ log_entry
+ else
+ log_entry = message
+ log_entry += ": #{info.inspect}" if info
+ log_entry
+ end
end
end
end
end