Class: ActiveLdap::Base

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Reloadable::Deprecated, Reloadable::Subclasses
Defined in:
lib/active_ldap/base.rb

Overview

Base

Base is the primary class which contains all of the core ActiveLdap functionality. It is meant to only ever be subclassed by extension classes.

Constant Summary collapse

VALID_LDAP_MAPPING_OPTIONS =
[:dn_attribute, :prefix, :classes, :scope]
@@configurations =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = nil) {|_self| ... } ⇒ Base

new

Creates a new instance of Base initializing all class and all initialization. Defines local defaults. See examples If multiple values exist for dn_attribute, the first one put here will be authoritative

Yields:

  • (_self)

Yield Parameters:



653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
# File 'lib/active_ldap/base.rb', line 653

def initialize(attributes=nil)
  init_base
  @new_entry = true
  if attributes.is_a?(String) or attributes.is_a?(Array)
    apply_object_class(required_classes)
    self.dn = attributes
  elsif attributes.is_a?(Hash)
    classes, attributes = extract_object_class(attributes)
    apply_object_class(classes | required_classes)
    normalized_attributes = {}
    attributes.each do |key, value|
      real_key = to_real_attribute_name(key)
      normalized_attributes[real_key] = value if real_key
    end
    self.dn = normalized_attributes[dn_attribute]
    self.attributes = normalized_attributes
  end
  yield self if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

method_missing

If a given method matches an attribute or an attribute alias then call the appropriate method. TODO: Determine if it would be better to define each allowed method using class_eval instead of using method_missing. This would give tab completion in irb.



793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
# File 'lib/active_ldap/base.rb', line 793

def method_missing(name, *args, &block)
  logger.debug {"stub: called method_missing" +
                  "(#{name.inspect}, #{args.inspect})"}
  ensure_apply_object_class

  key = name.to_s
  case key
  when /=$/
    real_key = $PREMATCH
    logger.debug {"method_missing: have_attribute? #{real_key}"}
    if have_attribute?(real_key, ['objectClass'])
      if args.size != 1
        raise ArgumentError,
                "wrong number of arguments (#{args.size} for 1)"
      end
      logger.debug {"method_missing: calling set_attribute" +
                      "(#{real_key}, #{args.inspect})"}
      return set_attribute(real_key, *args, &block)
    end
  when /(?:(_before_type_cast)|(\?))?$/
    real_key = $PREMATCH
    before_type_cast = !$1.nil?
    query = !$2.nil?
    logger.debug {"method_missing: have_attribute? #{real_key}"}
    if have_attribute?(real_key, ['objectClass'])
      if args.size > 1
        raise ArgumentError,
          "wrong number of arguments (#{args.size} for 1)"
      end
      if before_type_cast
        return get_attribute_before_type_cast(real_key, *args)
      elsif query
        return get_attribute_as_query(real_key, *args)
      else
        return get_attribute(real_key, *args)
      end
    end
  end
  super
end

Class Method Details

.add(dn, entries, options = {}) ⇒ Object



393
394
395
396
397
398
# File 'lib/active_ldap/base.rb', line 393

def add(dn, entries, options={})
  unnormalized_entries = entries.collect do |type, key, value|
    [type, key, unnormalize_attribute(key, value)]
  end
  connection.add(dn, unnormalized_entries, options)
end

.base ⇒ Object

Base.base

This method when included into Base provides an inheritable, overwritable configuration setting

This should be a string with the base of the ldap server such as 'dc=example,dc=com', and it should be overwritten by including configuration.rb into this class. When subclassing, the specified prefix will be concatenated.



315
316
317
318
319
320
321
322
# File 'lib/active_ldap/base.rb', line 315

def base
  _base = base_inheritable
  _base = configuration[:base] if _base.nil? and configuration
  _base ||= base_inheritable(true)
  [prefix, _base].find_all do |component|
    component and !component.empty?
  end.join(",")
end

.base_class ⇒ Object



472
473
474
475
476
477
478
# File 'lib/active_ldap/base.rb', line 472

def base_class
  if self == Base or superclass == Base
    self
  else
    superclass.base_class
  end
end

.base_inheritable ⇒ Object



304
# File 'lib/active_ldap/base.rb', line 304

alias_method :base_inheritable, :base

.class_local_attr_accessor(search_ancestors, *syms) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/active_ldap/base.rb', line 164

def self.class_local_attr_accessor(search_ancestors, *syms)
  syms.flatten.each do |sym|
    class_eval(<<-EOS, __FILE__, __LINE__ + 1)
      def self.#{sym}(search_superclasses=#{search_ancestors})
        @#{sym} ||= nil
        return @#{sym} if @#{sym}
        if search_superclasses
          target = superclass
          value = nil
          loop do
            break nil unless target.respond_to?("#{sym}")
            value = target.#{sym}
            break if value
            target = target.superclass
          end
          value
        else
          nil
        end
      end
      def #{sym}; self.class.#{sym}; end
      def self.#{sym}=(value); @#{sym} = value; end
      def #{sym}=(value); self.class.#{sym} = value; end
    EOS
  end
end

.create(attributes = nil, &block) ⇒ Object



238
239
240
241
242
243
244
245
246
# File 'lib/active_ldap/base.rb', line 238

def create(attributes=nil, &block)
  if attributes.is_a?(Array)
    attributes.collect {|attrs| create(attrs, &block)}
  else
    object = new(attributes, &block)
    object.save
    object
  end
end

.delete(targets, options = {}) ⇒ Object



373
374
375
376
377
378
379
# File 'lib/active_ldap/base.rb', line 373

def delete(targets, options={})
  targets = [targets] unless targets.is_a?(Array)
  targets = targets.collect do |target|
    ensure_dn_attribute(ensure_base(target))
  end
  connection.delete(targets, options)
end

.delete_all(filter = nil, options = {}) ⇒ Object



381
382
383
384
385
386
387
388
389
390
391
# File 'lib/active_ldap/base.rb', line 381

def delete_all(filter=nil, options={})
  options = {:base => base, :scope => ldap_scope}.merge(options)
  options = options.merge(:filter => filter) if filter
  targets = connection.search(options).collect do |dn, attributes|
    dn
  end.sort_by do |dn|
    dn.reverse
  end.reverse

  connection.delete(targets)
end

.destroy(targets, options = {}) ⇒ Object



352
353
354
355
356
357
# File 'lib/active_ldap/base.rb', line 352

def destroy(targets, options={})
  targets = [targets] unless targets.is_a?(Array)
  targets.each do |target|
    find(target, options).destroy
  end
end

.destroy_all(filter = nil, options = {}) ⇒ Object



359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/active_ldap/base.rb', line 359

def destroy_all(filter=nil, options={})
  targets = []
  if filter.is_a?(Hash)
    options = options.merge(filter)
    filter = nil
  end
  options = options.merge(:filter => filter) if filter
  find(:all, options).sort_by do |target|
    target.dn.reverse
  end.reverse.each do |target|
    target.destroy
  end
end

.dump(options = {}) ⇒ Object



335
336
337
338
339
340
341
342
# File 'lib/active_ldap/base.rb', line 335

def dump(options={})
  ldifs = []
  options = {:base => base, :scope => ldap_scope}.merge(options)
  connection.search(options) do |dn, attributes|
    ldifs << to_ldif(dn, attributes)
  end
  ldifs.join("\n")
end

.establish_connection(config = nil) ⇒ Object

Connect and bind to LDAP creating a class variable for use by all ActiveLdap objects.

config

config must be a hash that may contain any of the following fields: :password_block, :logger, :host, :port, :base, :bind_dn, :try_sasl, :allow_anonymous :bind_dn specifies the DN to bind with. :password_block specifies a Proc object that will yield a String to be used as the password when called. :logger specifies a preconfigured Log4r::Logger to be used for all logging :host sets the LDAP server hostname :port sets the LDAP server port :base overwrites Base.base - this affects EVERYTHING :try_sasl indicates that a SASL bind should be attempted when binding to the server (default: false) :allow_anonymous indicates that a true anonymous bind is allowed when trying to bind to the server (default: true) :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite. :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection :method - whether to use :ssl, :tls, or :plain (unencrypted) :retry_wait - seconds to wait before retrying a connection :ldap_scope - dictates how to find objects. ONELEVEL by default to avoid dn_attr collisions across OUs. Think before changing. :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned. :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true See lib/configuration.rb for defaults for each option



230
231
232
233
234
235
236
# File 'lib/active_ldap/base.rb', line 230

def establish_connection(config=nil)
  super
  ensure_logger
  connection.connect
  # Make irb users happy with a 'true'
  true
end

.exists?(dn, options = {}) ⇒ Boolean

Returns:

  • (Boolean)


426
427
428
429
430
431
432
# File 'lib/active_ldap/base.rb', line 426

def exists?(dn, options={})
  prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/
  suffix = /,#{Regexp.escape(base)}$/
  not search({:value => dn}.merge(options)).find do |_dn,|
    prefix.match(_dn) and suffix.match(_dn)
  end.nil?
end

.find(*args) ⇒ Object

find

Finds the first match for value where |value| is the value of some |field|, or the wildcard match. This is only useful for derived classes. usage: Subclass.find(:attribute => "cn", :value => "someval") Subclass.find('someval')



413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/active_ldap/base.rb', line 413

def find(*args)
  options = extract_options_from_args!(args)
  args = [:first] if args.empty? and !options.empty?
  case args.first
  when :first
    find_initial(options)
  when :all
    find_every(options)
  else
    find_from_dns(args, options)
  end
end

.human_attribute_name(attribute_key_name) ⇒ Object



480
481
482
# File 'lib/active_ldap/base.rb', line 480

def human_attribute_name(attribute_key_name)
  attribute_key_name.humanize
end

.ldap_mapping(options = {}) ⇒ Object

This class function is used to setup all mappings between the subclass and ldap for use in activeldap

Example:

ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
           :classes => ['top', 'posixAccount'],
           :scope => :sub


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/active_ldap/base.rb', line 288

def ldap_mapping(options={})
  validate_ldap_mapping_options(options)
  dn_attribute = options[:dn_attribute] || default_dn_attribute
  prefix = options[:prefix] || default_prefix
  classes = options[:classes]
  scope = options[:scope]

  self.dn_attribute = dn_attribute
  self.prefix = prefix
  self.ldap_scope = scope
  self.required_classes = classes

  public_class_method :new
  public_class_method :dn_attribute
end

.ldap_scope=(scope) ⇒ Object



325
326
327
328
329
330
331
332
333
# File 'lib/active_ldap/base.rb', line 325

def ldap_scope=(scope)
  scope = scope.to_sym if scope.is_a?(String)
  if scope.nil? or scope.is_a?(Symbol)
    self.ldap_scope_without_validation = scope
  else
    raise ConfigurationError,
            ":ldap_scope '#{scope.inspect}' must be a Symbol"
  end
end

.ldap_scope_without_validation= ⇒ Object



324
# File 'lib/active_ldap/base.rb', line 324

alias_method :ldap_scope_without_validation=, :ldap_scope=

.load(ldifs) ⇒ Object



348
349
350
# File 'lib/active_ldap/base.rb', line 348

def load(ldifs)
  connection.load(ldifs)
end

.modify(dn, entries, options = {}) ⇒ Object



400
401
402
403
404
405
# File 'lib/active_ldap/base.rb', line 400

def modify(dn, entries, options={})
  unnormalized_entries = entries.collect do |type, key, value|
    [type, key, unnormalize_attribute(key, value)]
  end
  connection.modify(dn, unnormalized_entries, options)
end

.search(options = {}, &block) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/active_ldap/base.rb', line 248

def search(options={}, &block)
  attr = options[:attribute]
  value = options[:value] || '*'
  filter = options[:filter]
  prefix = options[:prefix]

  value = value.first if value.is_a?(Array) and value.first.size == 1
  if filter.nil? and !value.is_a?(String)
    raise ArgumentError, "Search value must be a String"
  end

  _attr, value, _prefix = split_search_value(value)
  attr ||= _attr || dn_attribute || "objectClass"
  prefix ||= _prefix
  filter ||= "(#{attr}=#{escape_filter_value(value, true)})"
  _base = [prefix, base].compact.reject{|x| x.empty?}.join(",")
  connection.search(:base => _base,
                    :scope => options[:scope] || ldap_scope,
                    :filter => filter,
                    :limit => options[:limit],
                    :attributes => options[:attributes]) do |dn, attrs|
    attributes = {}
    attrs.each do |key, value|
      normalized_attr, normalized_value = make_subtypes(key, value)
      attributes[normalized_attr] ||= []
      attributes[normalized_attr].concat(normalized_value)
    end
    value = [dn, attributes]
    value = yield(value) if block_given?
    value
  end
end

.to_ldif(dn, attributes) ⇒ Object



344
345
346
# File 'lib/active_ldap/base.rb', line 344

def to_ldif(dn, attributes)
  connection.to_ldif(dn, unnormalize_attributes(attributes))
end

.update(dn, attributes, options = {}) ⇒ Object



434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/active_ldap/base.rb', line 434

def update(dn, attributes, options={})
  if dn.is_a?(Array)
    i = -1
    dns = dn
    dns.collect do |dn|
      i += 1
      update(dn, attributes[i], options)
    end
  else
    object = find(dn, options)
    object.update_attributes(attributes)
    object
  end
end

.update_all(attributes, filter = nil, options = {}) ⇒ Object



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/active_ldap/base.rb', line 449

def update_all(attributes, filter=nil, options={})
  search_options = options
  if filter
    if /[=\(\)&\|]/ =~ filter
      search_options = search_options.merge(:filter => filter)
    else
      search_options = search_options.merge(:value => filter)
    end
  end
  targets = search(search_options).collect do |dn, attrs|
    dn
  end

  entries = attributes.collect do |name, value|
    normalized_name, normalized_value = normalize_attribute(name, value)
    [:replace, normalized_name,
     unnormalize_attribute(normalized_name, normalized_value)]
  end
  targets.each do |dn|
    connection.modify(dn, entries, options)
  end
end

Instance Method Details

#==(comparison_object) ⇒ Object

Returns true if the comparison_object is the same object, or is of the same type and has the same dn.



675
676
677
678
679
680
# File 'lib/active_ldap/base.rb', line 675

def ==(comparison_object)
  comparison_object.equal?(self) or
    (comparison_object.instance_of?(self.class) and
     comparison_object.dn == dn and
     !comparison_object.new_entry?)
end

#[](name, force_array = false) ⇒ Object



930
931
932
933
934
935
936
# File 'lib/active_ldap/base.rb', line 930

def [](name, force_array=false)
  if name == "dn"
    array_of(dn, force_array)
  else
    get_attribute(name, force_array)
  end
end

#[]=(name, value) ⇒ Object



938
939
940
# File 'lib/active_ldap/base.rb', line 938

def []=(name, value)
  set_attribute(name, value)
end

#attribute_names ⇒ Object

attributes

Return attribute methods so that a program can determine available attributes dynamically without schema awareness



709
710
711
712
713
# File 'lib/active_ldap/base.rb', line 709

def attribute_names
  logger.debug {"stub: attribute_names called"}
  ensure_apply_object_class
  return @attr_methods.keys
end

#attribute_present?(name) ⇒ Boolean

Returns:

  • (Boolean)


715
716
717
718
# File 'lib/active_ldap/base.rb', line 715

def attribute_present?(name)
  values = get_attribute(name, true)
  !values.empty? or values.any? {|x| not (x and x.empty?)}
end

#attributes ⇒ Object

This returns the key value pairs in @data with all values cloned



866
867
868
# File 'lib/active_ldap/base.rb', line 866

def attributes
  Marshal.load(Marshal.dump(@data))
end

#attributes=(hash_or_assoc) ⇒ Object

This allows a bulk update to the attributes of a record without forcing an immediate save or validation.

It is unwise to attempt objectClass updates this way. Also be sure to only pass in key-value pairs of your choosing. Do not let URL/form hackers supply the keys.



876
877
878
879
880
881
# File 'lib/active_ldap/base.rb', line 876

def attributes=(hash_or_assoc)
  targets = remove_attributes_protected_from_mass_assignment(hash_or_assoc)
  targets.each do |key, value|
    set_attribute(key, value) if have_attribute?(key)
  end
end

#destroy ⇒ Object

destroy

Delete this entry from LDAP



761
762
763
764
765
766
767
768
769
# File 'lib/active_ldap/base.rb', line 761

def destroy
  logger.debug {"stub: delete called"}
  begin
    self.class.delete(dn)
    @new_entry = true
  rescue Error
    raise DeleteError.new("Failed to delete LDAP entry: '#{dn}'")
  end
end

#dn ⇒ Object

dn

Return the authoritative dn



737
738
739
740
741
742
743
744
745
746
747
# File 'lib/active_ldap/base.rb', line 737

def dn
  logger.debug {"stub: dn called"}
  dn_value = id
  if dn_value.nil?
    raise DistinguishedNameNotSetError.new,
            "#{dn_attribute} value of #{self} doesn't set"
  end
  _base = base
  _base = nil if _base.empty?
  ["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
end

#dn=(value) ⇒ Object Also known as: id=



753
754
755
# File 'lib/active_ldap/base.rb', line 753

def dn=(value)
  set_attribute(dn_attribute, value)
end

#each ⇒ Object



942
943
944
945
946
# File 'lib/active_ldap/base.rb', line 942

def each
  @data.each do |key, values|
    yield(key.dup, values.dup)
  end
end

#eql?(comparison_object) ⇒ Boolean

Delegates to ==

Returns:

  • (Boolean)


683
684
685
# File 'lib/active_ldap/base.rb', line 683

def eql?(comparison_object)
  self == (comparison_object)
end

#exists? ⇒ Boolean

exists?

Return whether the entry exists in LDAP or not

Returns:

  • (Boolean)


723
724
725
# File 'lib/active_ldap/base.rb', line 723

def exists?
  self.class.exists?(dn)
end

#hash ⇒ Object

Delegates to id in order to allow two records of the same type and id to work with something like:

[ User.find("a"), User.find("b"), User.find("c") ] &
[ User.find("a"), User.find("d") ] # => [ User.find("a") ]


691
692
693
# File 'lib/active_ldap/base.rb', line 691

def hash
  dn.hash
end

#have_attribute?(name, except = []) ⇒ Boolean Also known as: has_attribute?

Returns:

  • (Boolean)


910
911
912
913
# File 'lib/active_ldap/base.rb', line 910

def have_attribute?(name, except=[])
  real_name = to_real_attribute_name(name)
  real_name and !except.include?(real_name)
end

#id ⇒ Object



749
750
751
# File 'lib/active_ldap/base.rb', line 749

def id
  get_attribute(dn_attribute)
end

#may ⇒ Object



695
696
697
698
# File 'lib/active_ldap/base.rb', line 695

def may
  ensure_apply_object_class
  @may
end

#methods(inherited_too = true) ⇒ Object

Add available attributes to the methods



835
836
837
838
839
840
841
# File 'lib/active_ldap/base.rb', line 835

def methods(inherited_too=true)
  ensure_apply_object_class
  target_names = @attr_methods.keys + @attr_aliases.keys - ['objectClass']
  super + target_names.uniq.collect do |x|
    [x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
  end.flatten
end

#must ⇒ Object



700
701
702
703
# File 'lib/active_ldap/base.rb', line 700

def must
  ensure_apply_object_class
  @must
end

#new_entry? ⇒ Boolean

new_entry?

Return whether the entry is new entry in LDAP or not

Returns:

  • (Boolean)


730
731
732
# File 'lib/active_ldap/base.rb', line 730

def new_entry?
  @new_entry
end

#reload ⇒ Object

Raises:



916
917
918
919
920
921
922
923
924
925
926
927
928
# File 'lib/active_ldap/base.rb', line 916

def reload
  _, attributes = self.class.search(:value => id).find do |_dn, _attributes|
    dn == _dn
  end
  raise EntryNotFound, "Can't find dn '#{dn}' to reload" if attributes.nil?

  @ldap_data.update(attributes)
  classes, attributes = extract_object_class(attributes)
  apply_object_class(classes)
  self.attributes = attributes
  @new_entry = false
  self
end

#respond_to?(name, include_priv = false) ⇒ Boolean

Returns:

  • (Boolean)


844
845
846
847
848
849
# File 'lib/active_ldap/base.rb', line 844

def respond_to?(name, include_priv=false)
  have_attribute?(name.to_s) or
    (/(?:=|\?|_before_type_cast)$/ =~ name.to_s and
     have_attribute?($PREMATCH)) or
    super
end

#respond_to_without_attributes? ⇒ Object



843
# File 'lib/active_ldap/base.rb', line 843

alias_method :respond_to_without_attributes?, :respond_to?

#save ⇒ Object

save

Save and validate this object into LDAP either adding or replacing attributes TODO: Relative DN support



776
777
778
# File 'lib/active_ldap/base.rb', line 776

def save
  create_or_update
end

#save! ⇒ Object



780
781
782
783
784
# File 'lib/active_ldap/base.rb', line 780

def save!
  unless create_or_update
    raise EntryNotSaved, "entry #{dn} can't saved"
  end
end

#to_ldif ⇒ Object



883
884
885
# File 'lib/active_ldap/base.rb', line 883

def to_ldif
  self.class.to_ldif(dn, normalize_data(@data))
end

#to_xml(options = {}) ⇒ Object



887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
# File 'lib/active_ldap/base.rb', line 887

def to_xml(options={})
  root = options[:root] || Inflector.underscore(self.class.name)
  result = "<#{root}>\n"
  result << "  <dn>#{dn}</dn>\n"
  normalize_data(@data).sort_by {|key, values| key}.each do |key, values|
    targets = []
    values.each do |value|
      if value.is_a?(Hash)
        value.each do |option, real_value|
          targets << [real_value, " #{option}=\"true\""]
        end
      else
        targets << [value]
      end
    end
    targets.sort_by {|value, attr| value}.each do |value, attr|
      result << "  <#{key}#{attr}>#{value}</#{key}>\n"
    end
  end
  result << "</#{root}>\n"
  result
end

#update_attribute(name, value) ⇒ Object

Updates a given attribute and saves immediately



852
853
854
855
# File 'lib/active_ldap/base.rb', line 852

def update_attribute(name, value)
  set_attribute(name, value) if have_attribute?(name)
  save
end

#update_attributes(attrs) ⇒ Object

This performs a bulk update of attributes and immediately calls #save.



859
860
861
862
# File 'lib/active_ldap/base.rb', line 859

def update_attributes(attrs)
  self.attributes = attrs
  save
end