Class: ActiveLdap::Base

Inherits:
Object
  • Object
show all
Includes:
Enumerable, 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:



641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
# File 'lib/active_ldap/base.rb', line 641

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.


781
782
783
784
785
786
787
788
789
790
791
792
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
# File 'lib/active_ldap/base.rb', line 781

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



381
382
383
384
385
386
# File 'lib/active_ldap/base.rb', line 381

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

.baseObject

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.



303
304
305
306
307
308
309
310
# File 'lib/active_ldap/base.rb', line 303

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_classObject



460
461
462
463
464
465
466
# File 'lib/active_ldap/base.rb', line 460

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

.base_inheritableObject



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

alias_method :base_inheritable, :base

.class_local_attr_accessor(search_ancestors, *syms) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/active_ldap/base.rb', line 152

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



226
227
228
229
230
231
232
233
234
# File 'lib/active_ldap/base.rb', line 226

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



361
362
363
364
365
366
367
# File 'lib/active_ldap/base.rb', line 361

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



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

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



340
341
342
343
344
345
# File 'lib/active_ldap/base.rb', line 340

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



347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/active_ldap/base.rb', line 347

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



323
324
325
326
327
328
329
330
# File 'lib/active_ldap/base.rb', line 323

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



218
219
220
221
222
223
224
# File 'lib/active_ldap/base.rb', line 218

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)


414
415
416
417
418
419
420
# File 'lib/active_ldap/base.rb', line 414

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 => “some*val”)

Subclass.find('some*val')


401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/active_ldap/base.rb', line 401

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



468
469
470
# File 'lib/active_ldap/base.rb', line 468

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


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/active_ldap/base.rb', line 276

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



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

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



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

alias_method :ldap_scope_without_validation=, :ldap_scope=

.load(ldifs) ⇒ Object



336
337
338
# File 'lib/active_ldap/base.rb', line 336

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

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



388
389
390
391
392
393
# File 'lib/active_ldap/base.rb', line 388

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



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/active_ldap/base.rb', line 236

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



332
333
334
# File 'lib/active_ldap/base.rb', line 332

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

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



422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/active_ldap/base.rb', line 422

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



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/active_ldap/base.rb', line 437

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.



663
664
665
666
667
668
# File 'lib/active_ldap/base.rb', line 663

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



918
919
920
# File 'lib/active_ldap/base.rb', line 918

def [](name, force_array=false)
  get_attribute(name, force_array)
end

#[]=(name, value) ⇒ Object



922
923
924
# File 'lib/active_ldap/base.rb', line 922

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

#attribute_namesObject

attributes

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



697
698
699
700
701
# File 'lib/active_ldap/base.rb', line 697

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

#attribute_present?(name) ⇒ Boolean

Returns:

  • (Boolean)


703
704
705
706
# File 'lib/active_ldap/base.rb', line 703

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

#attributesObject

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



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

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.



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

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

#destroyObject

destroy

Delete this entry from LDAP



749
750
751
752
753
754
755
756
757
# File 'lib/active_ldap/base.rb', line 749

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

#dnObject

dn

Return the authoritative dn



725
726
727
728
729
730
731
732
733
734
735
# File 'lib/active_ldap/base.rb', line 725

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=



741
742
743
# File 'lib/active_ldap/base.rb', line 741

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

#eachObject



926
927
928
929
930
# File 'lib/active_ldap/base.rb', line 926

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

#eql?(comparison_object) ⇒ Boolean

Delegates to ==

Returns:

  • (Boolean)


671
672
673
# File 'lib/active_ldap/base.rb', line 671

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

#exists?Boolean

exists?

Return whether the entry exists in LDAP or not

Returns:

  • (Boolean)


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

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

#hashObject

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") ]


679
680
681
# File 'lib/active_ldap/base.rb', line 679

def hash
  dn.hash
end

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

Returns:

  • (Boolean)


898
899
900
901
# File 'lib/active_ldap/base.rb', line 898

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

#idObject



737
738
739
# File 'lib/active_ldap/base.rb', line 737

def id
  get_attribute(dn_attribute)
end

#mayObject



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

def may
  ensure_apply_object_class
  @may
end

#methods(inherited_too = true) ⇒ Object

Add available attributes to the methods



823
824
825
826
827
828
829
# File 'lib/active_ldap/base.rb', line 823

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

#mustObject



688
689
690
691
# File 'lib/active_ldap/base.rb', line 688

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)


718
719
720
# File 'lib/active_ldap/base.rb', line 718

def new_entry?
  @new_entry
end

#reloadObject

Raises:



904
905
906
907
908
909
910
911
912
913
914
915
916
# File 'lib/active_ldap/base.rb', line 904

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)


832
833
834
835
836
837
# File 'lib/active_ldap/base.rb', line 832

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



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

alias_method :respond_to_without_attributes?, :respond_to?

#saveObject

save

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



764
765
766
# File 'lib/active_ldap/base.rb', line 764

def save
  create_or_update
end

#save!Object



768
769
770
771
772
# File 'lib/active_ldap/base.rb', line 768

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

#to_ldifObject



871
872
873
# File 'lib/active_ldap/base.rb', line 871

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

#to_xml(options = {}) ⇒ Object



875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/active_ldap/base.rb', line 875

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



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

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.



847
848
849
850
# File 'lib/active_ldap/base.rb', line 847

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