Class ActiveLdap::Adapter::Base
In: lib/active_ldap/adapter/ldap.rb
lib/active_ldap/adapter/net_ldap.rb
lib/active_ldap/adapter/base.rb
Parent: Object
Error DeleteError AdapterNotSpecified OperationNotPermitted LdapError RequiredAttributeMissed AttributeAssignmentError RequiredObjectClassMissed DistinguishedNameNotSetError StrongAuthenticationRequired ConnectionError SaveError EntryNotFound AuthenticationError EntryNotSaved UnknownAttribute ConnectionNotEstablished TimeoutError ConfigurationError AdapterNotFound DistinguishedNameInvalid ObjectClassError EntryInvalid EntryAlreadyExist Reloadable::Deprecated Base Reloadable::Subclasses Enumerable Collection StandardError HasMany HasManyWrap BelongsToMany Proxy BelongsTo Base\n[lib/active_ldap/adapter/base.rb\nlib/active_ldap/adapter/ldap.rb\nlib/active_ldap/adapter/net_ldap.rb] Ldap NetLdap ActiveRecord::Callbacks ActiveRecord::Validations Schema DistinguishedName lib/active_ldap/base.rb lib/active_ldap/schema.rb lib/active_ldap/distinguished_name.rb lib/active_ldap/ldap_error.rb ClassMethods Associations lib/active_ldap/association/has_many_wrap.rb lib/active_ldap/association/has_many.rb lib/active_ldap/association/proxy.rb lib/active_ldap/association/collection.rb lib/active_ldap/association/belongs_to_many.rb lib/active_ldap/association/belongs_to.rb Association ClassMethods Configuration Command lib/active_ldap/adapter/net_ldap.rb lib/active_ldap/adapter/ldap.rb lib/active_ldap/adapter/base.rb Adapter ClassMethods Attributes ClassMethods ObjectClass Callbacks ClassMethods Connection Validations Salt UserPassword ActiveLdap dot/m_26_0.png

Methods

Constants

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]
LOGICAL_OPERATORS = [:and, :or, :&, :|]

Public Class methods

[Source]

    # File lib/active_ldap/adapter/ldap.rb, line 7
 7:         def ldap_connection(options)
 8:           unless defined?(::LDAP)
 9:             require 'active_ldap/adapter/ldap_ext'
10:           end
11:           Ldap.new(options)
12:         end

[Source]

    # File lib/active_ldap/adapter/net_ldap.rb, line 9
 9:         def net_ldap_connection(options)
10:           unless defined?(::Net::LDAP)
11:             require 'active_ldap/adapter/net_ldap_ext'
12:           end
13:           NetLdap.new(options)
14:         end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 13
13:       def initialize(configuration={})
14:         @connection = nil
15:         @configuration = configuration.dup
16:         @logger = @configuration.delete(:logger)
17:         @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
18:         VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
19:           instance_variable_set("@#{name}", configuration[name])
20:         end
21:       end

Public Instance methods

[Source]

     # File lib/active_ldap/adapter/base.rb, line 161
161:       def add(dn, entries, options={})
162:         begin
163:           operation(options) do
164:             yield(dn, entries)
165:           end
166:         rescue LdapError::NoSuchObject
167:           raise EntryNotFound, "No such entry: #{dn}"
168:         rescue LdapError::InvalidDnSyntax
169:           raise DistinguishedNameInvalid.new(dn)
170:         rescue LdapError::AlreadyExists
171:           raise EntryAlreadyExist, "#{$!.message}: #{dn}"
172:         rescue LdapError::StrongAuthRequired
173:           raise StrongAuthenticationRequired, "#{$!.message}: #{dn}"
174:         rescue LdapError::ObjectClassViolation
175:           raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
176:         rescue LdapError::UnwillingToPerform
177:           raise OperationNotPermitted, "#{$!.message}: #{dn}"
178:         end
179:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 43
43:       def bind(options={})
44:         bind_dn = options[:bind_dn] || @bind_dn
45:         try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
46:         if options.has_key?(:allow_anonymous)
47:           allow_anonymous = options[:allow_anonymous]
48:         else
49:           allow_anonymous = @allow_anonymous
50:         end
51: 
52:         # Rough bind loop:
53:         # Attempt 1: SASL if available
54:         # Attempt 2: SIMPLE with credentials if password block
55:         # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
56:         if try_sasl and sasl_bind(bind_dn, options)
57:           @logger.info {'Bound SASL'}
58:         elsif simple_bind(bind_dn, options)
59:           @logger.info {'Bound simple'}
60:         elsif allow_anonymous and bind_as_anonymous(options)
61:           @logger.info {'Bound anonymous'}
62:         else
63:           message = yield if block_given?
64:           message ||= 'All authentication methods exhausted.'
65:           raise AuthenticationError, message
66:         end
67: 
68:         bound?
69:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 71
71:       def bind_as_anonymous(options={})
72:         @logger.info {"Attempting anonymous authentication"}
73:         operation(options) do
74:           yield
75:         end
76:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 23
23:       def connect(options={})
24:         host = options[:host] || @host
25:         port = options[:port] || @port
26:         method = ensure_method(options[:method] || @method)
27:         @connection = yield(host, port, method)
28:         prepare_connection(options)
29:         bind(options)
30:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 78
78:       def connecting?
79:         not @connection.nil?
80:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 146
146:       def delete(targets, options={})
147:         targets = [targets] unless targets.is_a?(Array)
148:         return if targets.empty?
149:         target = nil
150:         begin
151:           operation(options) do
152:             targets.each do |target|
153:               yield(target)
154:             end
155:           end
156:         rescue LdapError::NoSuchObject
157:           raise EntryNotFound, "No such entry: #{target}"
158:         end
159:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 32
32:       def disconnect!(options={})
33:         return if @connection.nil?
34:         unbind(options)
35:         @connection = nil
36:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 109
109:       def load(ldifs, options={})
110:         operation(options) do
111:           ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
112:             yield(ldif)
113:           end
114:         end
115:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 181
181:       def modify(dn, entries, options={})
182:         begin
183:           operation(options) do
184:             yield(dn, entries)
185:           end
186:         rescue LdapError::UndefinedType
187:           raise
188:         rescue LdapError::ObjectClassViolation
189:           raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
190:         end
191:       end

[Source]

    # File lib/active_ldap/adapter/base.rb, line 38
38:       def rebind(options={})
39:         unbind(options) if bound?
40:         connect(options)
41:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 82
 82:       def schema(options={})
 83:         @schema ||= operation(options) do
 84:           base = options[:base]
 85:           attrs = options[:attributes]
 86: 
 87:           attrs ||= [
 88:             'objectClasses',
 89:             'attributeTypes',
 90:             'matchingRules',
 91:             'matchingRuleUse',
 92:             'dITStructureRules',
 93:             'dITContentRules',
 94:             'nameForms',
 95:             'ldapSyntaxes',
 96:             #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER.
 97:           ]
 98:           key = 'subschemaSubentry'
 99:           base ||= root_dse([key], options)[0][key][0]
100:           base ||= 'cn=schema'
101:           dn, attributes = search(:base => base,
102:                                   :scope => :base,
103:                                   :filter => '(objectClass=subschema)',
104:                                   :attributes => attrs).first
105:           Schema.new(attributes)
106:         end
107:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 117
117:       def search(options={})
118:         filter = parse_filter(options[:filter] || 'objectClass=*')
119:         attrs = options[:attributes] || []
120:         scope = ensure_scope(options[:scope])
121:         base = options[:base]
122:         limit = options[:limit] || 0
123:         limit = nil if limit <= 0
124: 
125:         attrs = attrs.to_a # just in case
126: 
127:         values = []
128:         callback = Proc.new do |value, block|
129:           value = block.call(value) if block
130:           values << value
131:         end
132: 
133:         begin
134:           operation(options) do
135:             yield(base, scope, filter, attrs, limit, callback)
136:         end
137:         rescue LdapError
138:           # Do nothing on failure
139:           @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " +
140:                          "for #{filter} and attrs #{attrs.inspect}"}
141:         end
142: 
143:         values
144:       end

Private Instance methods

[Source]

     # File lib/active_ldap/adapter/base.rb, line 399
399:       def assert_filter_logical_operator(operator)
400:         return if operator.nil?
401:         unless filter_logical_operator?(operator)
402:           raise ArgumentError,
403:                 "invalid logical operator: #{operator.inspect}: " +
404:                 "available operators: #{LOGICAL_OPERATORS.inspect}"
405:         end
406:       end

Determine if we have exceed the retry limit or not. True is reconnecting is allowed - False if not.

[Source]

     # File lib/active_ldap/adapter/base.rb, line 453
453:       def can_reconnect?(options={})
454:         retry_limit = options[:retry_limit] || @retry_limit
455:         reconnect_attempts = options[:reconnect_attempts] || 0
456: 
457:         retry_limit < 0 or reconnect_attempts < (retry_limit - 1)
458:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 380
380:       def collection?(object)
381:         !object.is_a?(String) and object.respond_to?(:each)
382:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 350
350:       def construct_component(key, value, operator=nil)
351:         if collection?(value)
352:           values = []
353:           value.each do |val|
354:             if collection?(val)
355:               values.concat(val.collect {|v| [key, v]})
356:             else
357:               values << [key, val]
358:             end
359:           end
360:           values[0] = values[0][1] if filter_logical_operator?(values[0][1])
361:           parse_filter(values, operator)
362:         else
363:           "(#{key}=#{value})"
364:         end
365:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 367
367:       def construct_filter(components, operator=nil)
368:         operator = normalize_filter_logical_operator(operator)
369:         components = components.compact
370:         case components.size
371:         when 0
372:           nil
373:         when 1
374:           components.join
375:         else
376:           "(#{operator == :and ? '&' : '|'}#{components.join})"
377:         end
378:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 385
385:       def filter_logical_operator?(operator)
386:         LOGICAL_OPERATORS.include?(operator)
387:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 206
206:       def need_credential_sasl_mechanism?(mechanism)
207:         not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism)
208:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 340
340:       def normalize_array_filter(filter, operator=nil)
341:         filter_operator, *components = filter
342:         if filter_logical_operator?(filter_operator)
343:           operator = filter_operator
344:         else
345:           components.unshift(filter_operator)
346:         end
347:         [operator, components]
348:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 389
389:       def normalize_filter_logical_operator(operator)
390:         assert_filter_logical_operator(operator)
391:         case (operator || :and)
392:         when :and, :&
393:           :and
394:         else
395:           :or
396:         end
397:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 197
197:       def operation(options)
198:         reconnect_if_need
199:         try_reconnect = !options.has_key?(:try_reconnect) ||
200:                            options[:try_reconnect]
201:         with_timeout(try_reconnect, options) do
202:           yield
203:         end
204:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 292
292:       def parse_filter(filter, operator=nil)
293:         return nil if filter.nil?
294:         if !filter.is_a?(String) and !filter.respond_to?(:collect)
295:           filter = filter.to_s
296:         end
297: 
298:         case filter
299:         when String
300:           parse_filter_string(filter)
301:         when Hash
302:           components = filter.sort_by {|k, v| k.to_s}.collect do |key, value|
303:             construct_component(key, value, operator)
304:           end
305:           construct_filter(components, operator)
306:         else
307:           operator, components = normalize_array_filter(filter, operator)
308: 
309:           components = components.collect do |component|
310:             if component.is_a?(Array) and component.size == 2
311:               key, value = component
312:               if filter_logical_operator?(key) or value.is_a?(Hash)
313:                 parse_filter(value, key)
314:               else
315:                 construct_component(key, value, operator)
316:               end
317:             elsif component.is_a?(Symbol)
318:               assert_filter_logical_operator(component)
319:               nil
320:             else
321:               parse_filter(component, operator)
322:             end
323:           end
324:           construct_filter(components, operator)
325:         end
326:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 328
328:       def parse_filter_string(filter)
329:         if /\A\s*\z/.match(filter)
330:           nil
331:         else
332:           if filter[0, 1] == "("
333:             filter
334:           else
335:             "(#{filter})"
336:           end
337:         end
338:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 210
210:       def password(bind_dn, options={})
211:         passwd = options[:password] || @password
212:         return passwd if passwd
213: 
214:         password_block = options[:password_block] || @password_block
215:         # TODO: Give a warning to reconnect users with password clearing
216:         # Get the passphrase for the first time, or anew if we aren't storing
217:         if password_block.respond_to?(:call)
218:           passwd = password_block.call(bind_dn)
219:         else
220:           @logger.error {'password_block not nil or Proc object. Ignoring.'}
221:           return nil
222:         end
223: 
224:         # Store the password for quick reference later
225:         if options.has_key?(:store_password)
226:           store_password = options[:store_password]
227:         else
228:           store_password = @store_password
229:         end
230:         @password = store_password ? passwd : nil
231: 
232:         passwd
233:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 194
194:       def prepare_connection(options)
195:       end

Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.

[Source]

     # File lib/active_ldap/adapter/base.rb, line 410
410:       def reconnect(options={})
411:         options = options.dup
412:         force = options[:force]
413:         retry_limit = options[:retry_limit] || @retry_limit
414:         retry_wait = options[:retry_wait] || @retry_wait
415:         options[:reconnect_attempts] ||= 0
416: 
417:         loop do
418:           unless can_reconnect?(options)
419:             raise ConnectionError,
420:                   'Giving up trying to reconnect to LDAP server.'
421:           end
422: 
423:           @logger.debug {'Attempting to reconnect'}
424:           disconnect!
425: 
426:           # Reset the attempts if this was forced.
427:           options[:reconnect_attempts] = 0 if force
428:           options[:reconnect_attempts] += 1 if retry_limit >= 0
429:           begin
430:             connect(options)
431:             break
432:           rescue => detail
433:             @logger.error {"Reconnect to server failed: #{detail.exception}"}
434:             @logger.error {"Reconnect to server failed backtrace:\n" +
435:                             detail.backtrace.join("\n")}
436:             # Do not loop if forced
437:             raise ConnectionError, detail.message if force
438:           end
439: 
440:           # Sleep before looping
441:           sleep retry_wait
442:         end
443: 
444:         true
445:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 447
447:       def reconnect_if_need(options={})
448:         reconnect(options) if !connecting? and can_reconnect?(options)
449:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 246
246:       def sasl_bind(bind_dn, options={})
247:         return false unless bind_dn
248: 
249:         # Get all SASL mechanisms
250:         mechanisms = operation(options) do
251:           key = "supportedSASLMechanisms"
252:           root_dse([key])[0][key]
253:         end
254:         mechanisms ||= []
255: 
256:         if options.has_key?(:sasl_quiet)
257:           sasl_quiet = options[:sasl_quiet]
258:         else
259:           sasl_quiet = @sasl_quiet
260:         end
261: 
262:         sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
263:         sasl_mechanisms.each do |mechanism|
264:           next unless mechanisms.include?(mechanism)
265:           operation(options) do
266:             yield(bind_dn, mechanism, sasl_quiet)
267:             return true if bound?
268:           end
269:         end
270:         false
271:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 273
273:       def simple_bind(bind_dn, options={})
274:         return false unless bind_dn
275: 
276:         passwd = password(bind_dn, options)
277:         return false unless passwd
278: 
279:         begin
280:           operation(options) do
281:             yield(bind_dn, passwd)
282:             bound?
283:           end
284:         rescue LdapError::InvalidDnSyntax
285:           @logger.debug {"DN is invalid: #{bind_dn}"}
286:           raise DistinguishedNameInvalid.new(bind_dn)
287:         rescue LdapError::InvalidCredentials
288:           false
289:         end
290:       end

[Source]

     # File lib/active_ldap/adapter/base.rb, line 235
235:       def with_timeout(try_reconnect=true, options={}, &block)
236:         begin
237:           Timeout.alarm(@timeout, &block)
238:         rescue Timeout::Error => e
239:           @logger.error {'Requested action timed out.'}
240:           retry if try_reconnect and @retry_on_timeout and reconnect(options)
241:           @logger.error {e.message}
242:           raise TimeoutError, e.message
243:         end
244:       end

[Validate]