| Class | ActiveLdap::Base |
| In: |
lib/active_ldap/base.rb
|
| Parent: | Object |
Base is the primary class which contains all of the core ActiveLdap functionality. It is meant to only ever be subclassed by extension classes.
| VALID_LDAP_MAPPING_OPTIONS | = | [:dn_attribute, :prefix, :scope, :classes, :recommended_classes] |
| VALID_SEARCH_OPTIONS | = | [:attribute, :value, :filter, :prefix, :classes, :scope, :limit, :attributes, :sort_by, :order] |
| base | -> | base_inheritable |
| ldap_scope= | -> | ldap_scope_without_validation= |
| respond_to? | -> | respond_to_without_attributes? |
| base | -> | base_of_class |
# File lib/active_ldap/base.rb, line 422
422: def add(dn, entries, options={})
423: unnormalized_entries = entries.collect do |type, key, value|
424: [type, key, unnormalize_attribute(key, value)]
425: end
426: connection.add(dn, unnormalized_entries, options)
427: end
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.
# File lib/active_ldap/base.rb, line 344
344: def base
345: _base = base_inheritable
346: _base = configuration[:base] if _base.nil? and configuration
347: _base ||= base_inheritable(true)
348: [prefix, _base].find_all do |component|
349: component and !component.empty?
350: end.join(",")
351: end
# File lib/active_ldap/base.rb, line 511
511: def base_class
512: if self == Base or superclass == Base
513: self
514: else
515: superclass.base_class
516: end
517: end
# File lib/active_ldap/base.rb, line 179
179: def self.class_local_attr_accessor(search_ancestors, *syms)
180: syms.flatten.each do |sym|
181: class_eval("def self.\#{sym}(search_superclasses=\#{search_ancestors})\n@\#{sym} ||= nil\nreturn @\#{sym} if @\#{sym}\nif search_superclasses\ntarget = superclass\nvalue = nil\nloop do\nbreak nil unless target.respond_to?(\"\#{sym}\")\nvalue = target.\#{sym}\nbreak if value\ntarget = target.superclass\nend\nvalue\nelse\nnil\nend\nend\ndef \#{sym}; self.class.\#{sym}; end\ndef self.\#{sym}=(value); @\#{sym} = value; end\ndef \#{sym}=(value); self.class.\#{sym} = value; end\n", __FILE__, __LINE__ + 1)
182: end
183: end
# File lib/active_ldap/base.rb, line 256
256: def create(attributes=nil, &block)
257: if attributes.is_a?(Array)
258: attributes.collect {|attrs| create(attrs, &block)}
259: else
260: object = new(attributes, &block)
261: object.save
262: object
263: end
264: end
# File lib/active_ldap/base.rb, line 402
402: def delete(targets, options={})
403: targets = [targets] unless targets.is_a?(Array)
404: targets = targets.collect do |target|
405: ensure_dn_attribute(ensure_base(target))
406: end
407: connection.delete(targets, options)
408: end
# File lib/active_ldap/base.rb, line 410
410: def delete_all(filter=nil, options={})
411: options = {:base => base, :scope => ldap_scope}.merge(options)
412: options = options.merge(:filter => filter) if filter
413: targets = connection.search(options).collect do |dn, attributes|
414: dn
415: end.sort_by do |dn|
416: dn.reverse
417: end.reverse
418:
419: connection.delete(targets)
420: end
# File lib/active_ldap/base.rb, line 381
381: def destroy(targets, options={})
382: targets = [targets] unless targets.is_a?(Array)
383: targets.each do |target|
384: find(target, options).destroy
385: end
386: end
# File lib/active_ldap/base.rb, line 388
388: def destroy_all(filter=nil, options={})
389: targets = []
390: if filter.is_a?(Hash)
391: options = options.merge(filter)
392: filter = nil
393: end
394: options = options.merge(:filter => filter) if filter
395: find(:all, options).sort_by do |target|
396: target.dn.reverse
397: end.reverse.each do |target|
398: target.destroy
399: end
400: end
# File lib/active_ldap/base.rb, line 364
364: def dump(options={})
365: ldifs = []
366: options = {:base => base, :scope => ldap_scope}.merge(options)
367: connection.search(options) do |dn, attributes|
368: ldifs << to_ldif(dn, attributes)
369: end
370: ldifs.join("\n")
371: end
Connect and bind to LDAP creating a class variable for use by all ActiveLdap objects.
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)
:sasl_mechanisms is an array of SASL mechanism to try
(default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
: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
# File lib/active_ldap/base.rb, line 248
248: def establish_connection(config=nil)
249: super
250: ensure_logger
251: connection.connect
252: # Make irb users happy with a 'true'
253: true
254: end
# File lib/active_ldap/base.rb, line 455
455: def exists?(dn, options={})
456: prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/ #
457: dn_suffix = nil
458: not search({:value => dn}.merge(options)).find do |_dn,|
459: if prefix.match(_dn)
460: begin
461: dn_suffix ||= DN.parse(base)
462: dn_prefix = DN.parse(_dn) - dn_suffix
463: true
464: rescue DistinguishedNameInvalid, ArgumentError
465: false
466: end
467: else
468: false
469: end
470: end.nil?
471: end
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')
# File lib/active_ldap/base.rb, line 442
442: def find(*args)
443: options = extract_options_from_args!(args)
444: args = [:first] if args.empty? and !options.empty?
445: case args.first
446: when :first
447: find_initial(options)
448: when :all
449: find_every(options)
450: else
451: find_from_dns(args, options)
452: end
453: end
# File lib/active_ldap/base.rb, line 519
519: def human_attribute_name(attribute_key_name)
520: attribute_key_name.humanize
521: end
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
# File lib/active_ldap/base.rb, line 316
316: def ldap_mapping(options={})
317: validate_ldap_mapping_options(options)
318: dn_attribute = options[:dn_attribute] || default_dn_attribute
319: prefix = options[:prefix] || default_prefix
320: classes = options[:classes]
321: recommended_classes = options[:recommended_classes]
322: scope = options[:scope]
323:
324: self.dn_attribute = dn_attribute
325: self.prefix = prefix
326: self.ldap_scope = scope
327: self.required_classes = classes
328: self.recommended_classes = recommended_classes
329:
330: public_class_method :new
331: end
# File lib/active_ldap/base.rb, line 354
354: def ldap_scope=(scope)
355: scope = scope.to_sym if scope.is_a?(String)
356: if scope.nil? or scope.is_a?(Symbol)
357: self.ldap_scope_without_validation = scope
358: else
359: raise ConfigurationError,
360: ":ldap_scope '#{scope.inspect}' must be a Symbol"
361: end
362: end
# File lib/active_ldap/base.rb, line 429
429: def modify(dn, entries, options={})
430: unnormalized_entries = entries.collect do |type, key, value|
431: [type, key, unnormalize_attribute(key, value)]
432: end
433: connection.modify(dn, unnormalized_entries, options)
434: end
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
# File lib/active_ldap/base.rb, line 765
765: def initialize(attributes=nil)
766: init_base
767: @new_entry = true
768: initial_classes = required_classes | recommended_classes
769: if attributes.nil?
770: apply_object_class(initial_classes)
771: elsif attributes.is_a?(String) or attributes.is_a?(Array)
772: apply_object_class(initial_classes)
773: self.dn = attributes
774: elsif attributes.is_a?(Hash)
775: classes, attributes = extract_object_class(attributes)
776: apply_object_class(classes | initial_classes)
777: normalized_attributes = {}
778: attributes.each do |key, value|
779: real_key = to_real_attribute_name(key)
780: normalized_attributes[real_key] = value if real_key
781: end
782: self.dn = normalized_attributes[dn_attribute]
783: self.attributes = normalized_attributes
784: else
785: message = "'#{attributes.inspect}' must be either "
786: message << "nil, DN value as String or Array or attributes as Hash"
787: raise ArgumentError, message
788: end
789: yield self if block_given?
790: end
# File lib/active_ldap/base.rb, line 266
266: def search(options={}, &block)
267: validate_search_options(options)
268: attr = options[:attribute]
269: value = options[:value] || '*'
270: filter = options[:filter]
271: prefix = options[:prefix]
272: classes = options[:classes]
273:
274: value = value.first if value.is_a?(Array) and value.first.size == 1
275: if filter.nil? and !value.is_a?(String)
276: raise ArgumentError, "Search value must be a String"
277: end
278:
279: _attr, value, _prefix = split_search_value(value)
280: attr ||= _attr || dn_attribute || "objectClass"
281: prefix ||= _prefix
282: if filter.nil?
283: filter = "(#{attr}=#{escape_filter_value(value, true)})"
284: filter = "(&#{filter}#{object_class_filters(classes)})"
285: end
286: _base = [prefix, base].compact.reject{|x| x.empty?}.join(",")
287: search_options = {
288: :base => _base,
289: :scope => options[:scope] || ldap_scope,
290: :filter => filter,
291: :limit => options[:limit],
292: :attributes => options[:attributes],
293: :sort_by => options[:sort_by],
294: :order => options[:order],
295: }
296: connection.search(search_options) do |dn, attrs|
297: attributes = {}
298: attrs.each do |key, value|
299: normalized_attr, normalized_value = make_subtypes(key, value)
300: attributes[normalized_attr] ||= []
301: attributes[normalized_attr].concat(normalized_value)
302: end
303: value = [dn, attributes]
304: value = yield(value) if block_given?
305: value
306: end
307: end
# File lib/active_ldap/base.rb, line 373
373: def to_ldif(dn, attributes)
374: connection.to_ldif(dn, unnormalize_attributes(attributes))
375: end
# File lib/active_ldap/base.rb, line 473
473: def update(dn, attributes, options={})
474: if dn.is_a?(Array)
475: i = -1
476: dns = dn
477: dns.collect do |dn|
478: i += 1
479: update(dn, attributes[i], options)
480: end
481: else
482: object = find(dn, options)
483: object.update_attributes(attributes)
484: object
485: end
486: end
# File lib/active_ldap/base.rb, line 488
488: def update_all(attributes, filter=nil, options={})
489: search_options = options
490: if filter
491: if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter
492: search_options = search_options.merge(:value => filter)
493: else
494: search_options = search_options.merge(:filter => filter)
495: end
496: end
497: targets = search(search_options).collect do |dn, attrs|
498: dn
499: end
500:
501: entries = attributes.collect do |name, value|
502: normalized_name, normalized_value = normalize_attribute(name, value)
503: [:replace, normalized_name,
504: unnormalize_attribute(normalized_name, normalized_value)]
505: end
506: targets.each do |dn|
507: connection.modify(dn, entries, options)
508: end
509: end
# File lib/active_ldap/base.rb, line 730
730: def default_dn_attribute
731: if name.empty?
732: dn_attribute = nil
733: parent_class = ancestors[1]
734: if parent_class.respond_to?(:dn_attribute)
735: dn_attribute = parent_class.dn_attribute
736: end
737: dn_attribute || "cn"
738: else
739: Inflector.underscore(Inflector.demodulize(name))
740: end
741: end
# File lib/active_ldap/base.rb, line 743
743: def default_prefix
744: if name.empty?
745: nil
746: else
747: "ou=#{Inflector.pluralize(Inflector.demodulize(name))}"
748: end
749: end
# File lib/active_ldap/base.rb, line 684
684: def ensure_base(target)
685: [truncate_base(target), base].join(',')
686: end
# File lib/active_ldap/base.rb, line 674
674: def ensure_dn(target)
675: attr, value, prefix = split_search_value(target)
676: "#{attr || dn_attribute}=#{value},#{prefix || base}"
677: end
# File lib/active_ldap/base.rb, line 679
679: def ensure_dn_attribute(target)
680: "#{dn_attribute}=" +
681: target.gsub(/^\s*#{Regexp.escape(dn_attribute)}\s*=\s*/i, '')
682: end
# File lib/active_ldap/base.rb, line 700
700: def ensure_logger
701: @@logger ||= configuration[:logger]
702: # Setup default logger to console
703: if @@logger.nil?
704: require 'log4r'
705: @@logger = Log4r::Logger.new('activeldap')
706: @@logger.level = Log4r::OFF
707: Log4r::StderrOutputter.new 'console'
708: @@logger.add('console')
709: end
710: configuration[:logger] ||= @@logger
711: end
# File lib/active_ldap/base.rb, line 664
664: def escape_filter_value(value, without_asterisk=false)
665: value.gsub(/[\*\(\)\\\0]/) do |x|
666: if without_asterisk and x == "*"
667: x
668: else
669: "\\%02x" % x[0]
670: end
671: end
672: end
# File lib/active_ldap/base.rb, line 532
532: def extract_options_from_args!(args)
533: args.last.is_a?(Hash) ? args.pop : {}
534: end
# File lib/active_ldap/base.rb, line 557
557: def find_every(options)
558: options = options.dup
559: sort_by = options.delete(:sort_by)
560: order = options.delete(:order)
561: limit = options.delete(:limit) if sort_by or order
562:
563: results = search(options).collect do |dn, attrs|
564: instantiate([dn, attrs])
565: end
566: return results if sort_by.nil? and order.nil?
567:
568: sort_by ||= "dn"
569: if sort_by.downcase == "dn"
570: results = results.sort_by {|result| DN.parse(result.dn)}
571: else
572: results = results.sort_by {|result| result.send(sort_by)}
573: end
574:
575: results.reverse! if normalize_sort_order(order || "ascend") == :descend
576: results = results[0, limit] if limit
577: results
578: end
# File lib/active_ldap/base.rb, line 580
580: def find_from_dns(dns, options)
581: expects_array = dns.first.is_a?(Array)
582: return [] if expects_array and dns.first.empty?
583:
584: dns = dns.flatten.compact.uniq
585:
586: case dns.size
587: when 0
588: raise EntryNotFound, "Couldn't find #{name} without a DN"
589: when 1
590: result = find_one(dns.first, options)
591: expects_array ? [result] : result
592: else
593: find_some(dns, options)
594: end
595: end
# File lib/active_ldap/base.rb, line 542
542: def find_initial(options)
543: find_every(options.merge(:limit => 1)).first
544: end
# File lib/active_ldap/base.rb, line 597
597: def find_one(dn, options)
598: attr, value, prefix = split_search_value(dn)
599: filters = [
600: "(#{attr || dn_attribute}=#{escape_filter_value(value, true)})",
601: object_class_filters(options[:classes]),
602: options[:filter],
603: ]
604: filter = "(&#{filters.compact.join('')})"
605: options = {:prefix => prefix}.merge(options.merge(:filter => filter))
606: result = find_initial(options)
607: if result
608: result
609: else
610: message = "Couldn't find #{name} with DN=#{dn}"
611: message << " #{options[:filter]}" if options[:filter]
612: raise EntryNotFound, message
613: end
614: end
# File lib/active_ldap/base.rb, line 616
616: def find_some(dns, options)
617: dn_filters = dns.collect do |dn|
618: attr, value, prefix = split_search_value(dn)
619: attr ||= dn_attribute
620: filter = "(#{attr}=#{escape_filter_value(value, true)})"
621: if prefix
622: filter = "(&#{filter}(dn=*,#{escape_filter_value(prefix)},#{base}))"
623: end
624: filter
625: end
626: filters = [
627: "(|#{dn_filters.join('')})",
628: object_class_filters(options[:classes]),
629: options[:filter],
630: ]
631: filter = "(&#{filters.compact.join('')})"
632: result = find_every(options.merge(:filter => filter))
633: if result.size == dns.size
634: result
635: else
636: message = "Couldn't find all #{name} with DNs (#{dns.join(', ')})"
637: message << " #{options[:filter]}"if options[:filter]
638: raise EntryNotFound, message
639: end
640: end
# File lib/active_ldap/base.rb, line 713
713: def instantiate(entry)
714: dn, attributes = entry
715: if self.class == Class
716: klass = self.ancestors[0].to_s.split(':').last
717: real_klass = self.ancestors[0]
718: else
719: klass = self.class.to_s.split(':').last
720: real_klass = self.class
721: end
722:
723: obj = real_klass.allocate
724: obj.instance_eval do
725: initialize_by_ldap_data(dn, attributes)
726: end
727: obj
728: end
# File lib/active_ldap/base.rb, line 546
546: def normalize_sort_order(value)
547: case value.to_s
548: when /\Aasc(?:end)?\z/i
549: :ascend
550: when /\Adesc(?:end)?\z/i
551: :descend
552: else
553: raise ArgumentError, "Invalid order: #{value.inspect}"
554: end
555: end
# File lib/active_ldap/base.rb, line 536
536: def object_class_filters(classes=nil)
537: (classes || required_classes).collect do |name|
538: "(objectClass=#{escape_filter_value(name, true)})"
539: end.join("")
540: end
# File lib/active_ldap/base.rb, line 642
642: def split_search_value(value)
643: attr = prefix = nil
644: begin
645: dn = DN.parse(value)
646: attr, value = dn.rdns.first.to_a.first
647: rest = dn.rdns[1..-1]
648: prefix = DN.new(*rest).to_s unless rest.empty?
649: rescue DistinguishedNameInvalid
650: begin
651: dn = DN.parse("DUMMY=#{value}")
652: _, value = dn.rdns.first.to_a.first
653: rest = dn.rdns[1..-1]
654: prefix = DN.new(*rest).to_s unless rest.empty?
655: rescue DistinguishedNameInvalid
656: end
657: end
658:
659: prefix = nil if prefix == base
660: prefix = truncate_base(prefix) if prefix
661: [attr, value, prefix]
662: end
# File lib/active_ldap/base.rb, line 688
688: def truncate_base(target)
689: if /,/ =~ target
690: begin
691: (DN.parse(target) - DN.parse(base)).to_s
692: rescue DistinguishedNameInvalid, ArgumentError
693: target
694: end
695: else
696: target
697: end
698: end
# File lib/active_ldap/base.rb, line 524
524: def validate_ldap_mapping_options(options)
525: options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
526: end
# File lib/active_ldap/base.rb, line 528
528: def validate_search_options(options)
529: options.assert_valid_keys(VALID_SEARCH_OPTIONS)
530: end
Returns true if the comparison_object is the same object, or is of the same type and has the same dn.
# File lib/active_ldap/base.rb, line 794
794: def ==(comparison_object)
795: comparison_object.equal?(self) or
796: (comparison_object.instance_of?(self.class) and
797: comparison_object.dn == dn and
798: !comparison_object.new_entry?)
799: end
# File lib/active_ldap/base.rb, line 1054
1054: def [](name, force_array=false)
1055: if name == "dn"
1056: array_of(dn, force_array)
1057: else
1058: get_attribute(name, force_array)
1059: end
1060: end
# File lib/active_ldap/base.rb, line 1062
1062: def []=(name, value)
1063: set_attribute(name, value)
1064: end
Return attribute methods so that a program can determine available attributes dynamically without schema awareness
# File lib/active_ldap/base.rb, line 828
828: def attribute_names
829: logger.debug {"stub: attribute_names called"}
830: ensure_apply_object_class
831: return @attr_methods.keys
832: end
# File lib/active_ldap/base.rb, line 834
834: def attribute_present?(name)
835: values = get_attribute(name, true)
836: !values.empty? or values.any? {|x| not (x and x.empty?)}
837: end
This returns the key value pairs in @data with all values cloned
# File lib/active_ldap/base.rb, line 990
990: def attributes
991: Marshal.load(Marshal.dump(@data))
992: end
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.
# File lib/active_ldap/base.rb, line 1000
1000: def attributes=(hash_or_assoc)
1001: targets = remove_attributes_protected_from_mass_assignment(hash_or_assoc)
1002: targets.each do |key, value|
1003: set_attribute(key, value) if have_attribute?(key)
1004: end
1005: end
Delete this entry from LDAP
# File lib/active_ldap/base.rb, line 884
884: def destroy
885: logger.debug {"stub: delete called"}
886: begin
887: self.class.delete(dn)
888: @new_entry = true
889: rescue Error
890: raise DeleteError.new("Failed to delete LDAP entry: '#{dn}'")
891: end
892: end
Return the authoritative dn
# File lib/active_ldap/base.rb, line 856
856: def dn
857: logger.debug {"stub: dn called"}
858: dn_value = id
859: if dn_value.nil?
860: raise DistinguishedNameNotSetError.new,
861: "#{dn_attribute} value of #{self} doesn't set"
862: end
863: _base = base
864: _base = nil if _base.empty?
865: ["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
866: end
# File lib/active_ldap/base.rb, line 876
876: def dn=(value)
877: set_attribute(dn_attribute, value)
878: end
# File lib/active_ldap/base.rb, line 1066
1066: def each
1067: @data.each do |key, values|
1068: yield(key.dup, values.dup)
1069: end
1070: end
Delegates to ==
# File lib/active_ldap/base.rb, line 802
802: def eql?(comparison_object)
803: self == (comparison_object)
804: end
# File lib/active_ldap/base.rb, line 1034
1034: def have_attribute?(name, except=[])
1035: real_name = to_real_attribute_name(name)
1036: real_name and !except.include?(real_name)
1037: end
# File lib/active_ldap/base.rb, line 814
814: def may
815: ensure_apply_object_class
816: @may
817: end
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.
# File lib/active_ldap/base.rb, line 916
916: def method_missing(name, *args, &block)
917: logger.debug {"stub: called method_missing" +
918: "(#{name.inspect}, #{args.inspect})"}
919: ensure_apply_object_class
920:
921: key = name.to_s
922: case key
923: when /=$/
924: real_key = $PREMATCH
925: logger.debug {"method_missing: have_attribute? #{real_key}"}
926: if have_attribute?(real_key, ['objectClass'])
927: if args.size != 1
928: raise ArgumentError,
929: "wrong number of arguments (#{args.size} for 1)"
930: end
931: logger.debug {"method_missing: calling set_attribute" +
932: "(#{real_key}, #{args.inspect})"}
933: return set_attribute(real_key, *args, &block)
934: end
935: when /(?:(_before_type_cast)|(\?))?$/
936: real_key = $PREMATCH
937: before_type_cast = !$1.nil?
938: query = !$2.nil?
939: logger.debug {"method_missing: have_attribute? #{real_key}"}
940: if have_attribute?(real_key, ['objectClass'])
941: if args.size > 1
942: raise ArgumentError,
943: "wrong number of arguments (#{args.size} for 1)"
944: end
945: if before_type_cast
946: return get_attribute_before_type_cast(real_key, *args)
947: elsif query
948: return get_attribute_as_query(real_key, *args)
949: else
950: return get_attribute(real_key, *args)
951: end
952: end
953: end
954: super
955: end
Add available attributes to the methods
# File lib/active_ldap/base.rb, line 958
958: def methods(inherited_too=true)
959: ensure_apply_object_class
960: target_names = @attr_methods.keys + @attr_aliases.keys
961: target_names -= ['objectClass', Inflector.underscore('objectClass')]
962: super + target_names.uniq.collect do |x|
963: [x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
964: end.flatten
965: end
# File lib/active_ldap/base.rb, line 819
819: def must
820: ensure_apply_object_class
821: @must
822: end
# File lib/active_ldap/base.rb, line 1040
1040: def reload
1041: _, attributes = self.class.search(:value => id).find do |_dn, _attributes|
1042: dn == _dn
1043: end
1044: raise EntryNotFound, "Can't find dn '#{dn}' to reload" if attributes.nil?
1045:
1046: @ldap_data.update(attributes)
1047: classes, attributes = extract_object_class(attributes)
1048: apply_object_class(classes)
1049: self.attributes = attributes
1050: @new_entry = false
1051: self
1052: end
# File lib/active_ldap/base.rb, line 968
968: def respond_to?(name, include_priv=false)
969: have_attribute?(name.to_s) or
970: (/(?:=|\?|_before_type_cast)$/ =~ name.to_s and
971: have_attribute?($PREMATCH)) or
972: super
973: end
Save and validate this object into LDAP either adding or replacing attributes TODO: Relative DN support
# File lib/active_ldap/base.rb, line 899
899: def save
900: create_or_update
901: end
# File lib/active_ldap/base.rb, line 903
903: def save!
904: unless create_or_update
905: raise EntryNotSaved, "entry #{dn} can't saved"
906: end
907: end
# File lib/active_ldap/base.rb, line 1007
1007: def to_ldif
1008: self.class.to_ldif(dn, normalize_data(@data))
1009: end
# File lib/active_ldap/base.rb, line 1011
1011: def to_xml(options={})
1012: root = options[:root] || Inflector.underscore(self.class.name)
1013: result = "<#{root}>\n"
1014: result << " <dn>#{dn}</dn>\n"
1015: normalize_data(@data).sort_by {|key, values| key}.each do |key, values|
1016: targets = []
1017: values.each do |value|
1018: if value.is_a?(Hash)
1019: value.each do |option, real_value|
1020: targets << [real_value, " #{option}=\"true\""]
1021: end
1022: else
1023: targets << [value]
1024: end
1025: end
1026: targets.sort_by {|value, attr| value}.each do |value, attr|
1027: result << " <#{key}#{attr}>#{value}</#{key}>\n"
1028: end
1029: end
1030: result << "</#{root}>\n"
1031: result
1032: end
Updates a given attribute and saves immediately
# File lib/active_ldap/base.rb, line 976
976: def update_attribute(name, value)
977: set_attribute(name, value) if have_attribute?(name)
978: save
979: end
This performs a bulk update of attributes and immediately calls save.
# File lib/active_ldap/base.rb, line 983
983: def update_attributes(attrs)
984: self.attributes = attrs
985: save
986: end
objectClass= special case for updating appropriately This updates the objectClass entry in @data. It also updating all required and allowed attributes while removing defined attributes that are no longer valid given the new objectclasses.
# File lib/active_ldap/base.rb, line 1157
1157: def apply_object_class(val)
1158: logger.debug {"stub: objectClass=(#{val.inspect}) called"}
1159: new_oc = val
1160: new_oc = [val] if new_oc.class != Array
1161: new_oc = new_oc.uniq
1162: return new_oc if @last_oc == new_oc
1163:
1164: # Store for caching purposes
1165: @last_oc = new_oc.dup
1166:
1167: # Set the actual objectClass data
1168: define_attribute_methods('objectClass')
1169: replace_class(*new_oc)
1170:
1171: # Build |data| from schema
1172: # clear attr_method mapping first
1173: @attr_methods = {}
1174: @normalized_attr_names = {}
1175: @attr_aliases = {}
1176: @musts = {}
1177: @mays = {}
1178: new_oc.each do |objc|
1179: # get all attributes for the class
1180: attributes = schema.class_attributes(objc)
1181: @musts[objc] = attributes[:must]
1182: @mays[objc] = attributes[:may]
1183: end
1184: @must = normalize_attribute_names(@musts.values)
1185: @may = normalize_attribute_names(@mays.values)
1186: (@must + @may).uniq.each do |attr|
1187: # Update attr_method with appropriate
1188: define_attribute_methods(attr)
1189: end
1190: end
Returns the array form of a value, or not an array if false is passed in.
# File lib/active_ldap/base.rb, line 1330
1330: def array_of(value, to_a=true)
1331: logger.debug {"stub: called array_of" +
1332: "(#{value.inspect}, #{to_a.inspect})"}
1333: case value
1334: when Array
1335: if to_a or value.size > 1
1336: value.collect {|v| array_of(v, to_a)}
1337: else
1338: if value.empty?
1339: nil
1340: else
1341: array_of(value.first, to_a)
1342: end
1343: end
1344: when Hash
1345: if to_a
1346: [value]
1347: else
1348: result = {}
1349: value.each {|k, v| result[k] = array_of(v, to_a)}
1350: result
1351: end
1352: else
1353: to_a ? [value.to_s] : value.to_s
1354: end
1355: end
# File lib/active_ldap/base.rb, line 1199
1199: def base
1200: logger.debug {"stub: called base"}
1201: [@base, base_of_class].compact.join(",")
1202: end
# File lib/active_ldap/base.rb, line 1205
1205: def base=(object_local_base)
1206: @base = object_local_base
1207: end
# File lib/active_ldap/base.rb, line 1443
1443: def check_configuration
1444: unless dn_attribute
1445: raise ConfigurationError,
1446: "dn_attribute not set for this class: #{self.class}"
1447: end
1448: end
# File lib/active_ldap/base.rb, line 1418
1418: def collect_all_entries(data)
1419: dn_attr = to_real_attribute_name(dn_attribute)
1420: dn_value = data[dn_attr]
1421: logger.debug {'#collect_all_entries: adding all attribute value pairs'}
1422: logger.debug {"#collect_all_entries: adding " +
1423: "#{dn_attr.inspect} = #{dn_value.inspect}"}
1424:
1425: entries = []
1426: entries.push([:add, dn_attr, dn_value])
1427:
1428: oc_value = data['objectClass']
1429: logger.debug {"#collect_all_entries: adding objectClass = " +
1430: "#{oc_value.inspect}"}
1431: entries.push([:add, 'objectClass', oc_value])
1432: data.each do |key, value|
1433: next if value.empty? or key == 'objectClass' or key == dn_attr
1434:
1435: logger.debug {"#collect_all_entries: adding attribute to new " +
1436: "entry: #{key.inspect}: #{value.inspect}"}
1437: entries.push([:add, key, value])
1438: end
1439:
1440: entries
1441: end
# File lib/active_ldap/base.rb, line 1370
1370: def collect_modified_entries(ldap_data, data)
1371: entries = []
1372: # Now that all the subtypes will be treated as unique attributes
1373: # we can see what's changed and add anything that is brand-spankin'
1374: # new.
1375: logger.debug {'#collect_modified_entries: traversing ldap_data ' +
1376: 'determining replaces and deletes'}
1377: ldap_data.each do |k, v|
1378: value = data[k] || []
1379:
1380: next if v == value
1381:
1382: # Create mod entries
1383: if value.empty?
1384: # Since some types do not have equality matching rules,
1385: # delete doesn't work
1386: # Replacing with nothing is equivalent.
1387: logger.debug {"#save: removing attribute from existing entry: #{k}"}
1388: if !data.has_key?(k) and schema.binary_required?(k)
1389: value = [{'binary' => []}]
1390: end
1391: else
1392: # Ditched delete then replace because attribs with no equality
1393: # match rules will fails
1394: logger.debug {"#collect_modified_entries: updating attribute of" +
1395: " existing entry: #{k}: #{value.inspect}"}
1396: end
1397: entries.push([:replace, k, value])
1398: end
1399: logger.debug {'#collect_modified_entries: finished traversing' +
1400: ' ldap_data'}
1401: logger.debug {'#collect_modified_entries: traversing data ' +
1402: 'determining adds'}
1403: data.each do |k, v|
1404: value = v || []
1405: next if ldap_data.has_key?(k) or value.empty?
1406:
1407: # Detect subtypes and account for them
1408: logger.debug {"#save: adding attribute to existing entry: " +
1409: "#{k}: #{value.inspect}"}
1410: # REPLACE will function like ADD, but doesn't hit EQUALITY problems
1411: # TODO: Added equality(attr) to Schema
1412: entries.push([:replace, k, value])
1413: end
1414:
1415: entries
1416: end
# File lib/active_ldap/base.rb, line 1489
1489: def create
1490: prepare_data_for_saving do |data, ldap_data|
1491: entries = collect_all_entries(data)
1492: logger.debug {"#create: adding #{dn}"}
1493: self.class.add(dn, entries)
1494: logger.debug {"#create: add successful"}
1495: @new_entry = false
1496: true
1497: end
1498: end
# File lib/active_ldap/base.rb, line 1450
1450: def create_or_update
1451: new_entry? ? create : update
1452: end
Make a method entry for every alias of a valid attribute and map it onto the first attribute passed in.
# File lib/active_ldap/base.rb, line 1310
1310: def define_attribute_methods(attr)
1311: logger.debug {"stub: called define_attribute_methods(#{attr.inspect})"}
1312: return if @attr_methods.has_key? attr
1313: schema.attribute_aliases(attr).each do |ali|
1314: logger.debug {"associating #{ali} --> #{attr}"}
1315: @attr_methods[ali] = attr
1316: logger.debug {"associating #{Inflector.underscore(ali)}" +
1317: " --> #{attr}"}
1318: @attr_aliases[Inflector.underscore(ali)] = attr
1319: logger.debug {"associating #{normalize_attribute_name(ali)}" +
1320: " --> #{attr}"}
1321: @normalized_attr_names[normalize_attribute_name(ali)] = attr
1322: end
1323: logger.debug {"stub: leaving define_attribute_methods(#{attr.inspect})"}
1324: end
enforce_type applies your changes without attempting to write to LDAP. This means that if you set userCertificate to somebinary value, it will wrap it up correctly.
# File lib/active_ldap/base.rb, line 1130
1130: def enforce_type(key, value)
1131: logger.debug {"stub: enforce_type called"}
1132: ensure_apply_object_class
1133: # Enforce attribute value formatting
1134: result = self.class.normalize_attribute(key, value)[1]
1135: logger.debug {"stub: enforce_types done"}
1136: result
1137: end
# File lib/active_ldap/base.rb, line 1119
1119: def ensure_apply_object_class
1120: current_object_class = @data['objectClass']
1121: return if current_object_class.nil? or current_object_class == @last_oc
1122: apply_object_class(current_object_class)
1123: end
# File lib/active_ldap/base.rb, line 1073
1073: def extract_object_class(attributes)
1074: classes = []
1075: attrs = attributes.reject do |key, value|
1076: key = key.to_s
1077: if key == 'objectClass' or
1078: key.underscore == 'object_class' or
1079: key.downcase == 'objectclass'
1080: classes |= [value].flatten
1081: true
1082: else
1083: false
1084: end
1085: end
1086: [classes, attrs]
1087: end
# File lib/active_ldap/base.rb, line 1229
1229: def false_value?(value)
1230: value.nil? or value == false or value == [] or
1231: value == "false" or value == "FALSE" or value == ""
1232: end
Return the value of the attribute called by method_missing?
# File lib/active_ldap/base.rb, line 1212
1212: def get_attribute(name, force_array=false)
1213: logger.debug {"stub: called get_attribute" +
1214: "(#{name.inspect}, #{force_array.inspect}"}
1215: get_attribute_before_type_cast(name, force_array)
1216: end
# File lib/active_ldap/base.rb, line 1218
1218: def get_attribute_as_query(name, force_array=false)
1219: logger.debug {"stub: called get_attribute_as_query" +
1220: "(#{name.inspect}, #{force_array.inspect}"}
1221: value = get_attribute_before_type_cast(name, force_array)
1222: if force_array
1223: value.collect {|x| !false_value?(x)}
1224: else
1225: !false_value?(value)
1226: end
1227: end
# File lib/active_ldap/base.rb, line 1234
1234: def get_attribute_before_type_cast(name, force_array=false)
1235: logger.debug {"stub: called get_attribute_before_type_cast" +
1236: "(#{name.inspect}, #{force_array.inspect}"}
1237: attr = to_real_attribute_name(name)
1238:
1239: value = @data[attr] || []
1240: # Return a copy of the stored data
1241: if force_array
1242: value.dup
1243: else
1244: array_of(value.dup, false)
1245: end
1246: end
# File lib/active_ldap/base.rb, line 1089
1089: def init_base
1090: check_configuration
1091: init_instance_variables
1092: end
# File lib/active_ldap/base.rb, line 1139
1139: def init_instance_variables
1140: @data = {} # where the r/w entry data is stored
1141: @ldap_data = {} # original ldap entry data
1142: @attr_methods = {} # list of valid method calls for attributes used for
1143: # dereferencing
1144: @normalized_attr_names = {} # list of normalized attribute name
1145: @attr_aliases = {} # aliases of @attr_methods
1146: @last_oc = false # for use in other methods for "caching"
1147: @base = nil
1148: end
# File lib/active_ldap/base.rb, line 1094
1094: def initialize_by_ldap_data(dn, attributes)
1095: init_base
1096: @new_entry = false
1097: @ldap_data = attributes
1098: classes, attributes = extract_object_class(attributes)
1099: apply_object_class(classes)
1100: self.dn = dn
1101: self.attributes = attributes
1102: yield self if block_given?
1103: end
# File lib/active_ldap/base.rb, line 1192
1192: def normalize_attribute_names(names)
1193: names.flatten.uniq.collect do |may|
1194: schema.attribute_aliases(may).first
1195: end
1196: end
# File lib/active_ldap/base.rb, line 1357
1357: def normalize_data(data, except=[])
1358: result = {}
1359: data.each do |key, values|
1360: next if except.include?(key)
1361: real_name = to_real_attribute_name(key)
1362: next if real_name and except.include?(real_name)
1363: real_name ||= key
1364: result[real_name] ||= []
1365: result[real_name].concat(values)
1366: end
1367: result
1368: end
# File lib/active_ldap/base.rb, line 1454
1454: def prepare_data_for_saving
1455: logger.debug {"stub: save called"}
1456:
1457: # Expand subtypes to real ldap_data entries
1458: # We can't reuse @ldap_data because an exception would leave
1459: # an object in an unknown state
1460: logger.debug {"#save: expanding subtypes in @ldap_data"}
1461: ldap_data = normalize_data(@ldap_data)
1462: logger.debug {'#save: subtypes expanded for @ldap_data'}
1463:
1464: # Expand subtypes to real data entries, but leave @data alone
1465: logger.debug {'#save: expanding subtypes for @data'}
1466: bad_attrs = @data.keys - attribute_names
1467: data = normalize_data(@data, bad_attrs)
1468: logger.debug {'#save: subtypes expanded for @data'}
1469:
1470: success = yield(data, ldap_data)
1471:
1472: if success
1473: logger.debug {"#save: resetting @ldap_data to a dup of @data"}
1474: @ldap_data = Marshal.load(Marshal.dump(data))
1475: # Delete items disallowed by objectclasses.
1476: # They should have been removed from ldap.
1477: logger.debug {'#save: removing attributes from @ldap_data not ' +
1478: 'sent in data'}
1479: bad_attrs.each do |remove_me|
1480: @ldap_data.delete(remove_me)
1481: end
1482: logger.debug {'#save: @ldap_data reset complete'}
1483: end
1484:
1485: logger.debug {'stub: save exited'}
1486: success
1487: end
Set the value of the attribute called by method_missing?
# File lib/active_ldap/base.rb, line 1251
1251: def set_attribute(name, value)
1252: logger.debug {"stub: called set_attribute" +
1253: "(#{name.inspect}, #{value.inspect})"}
1254:
1255: # Get the attr and clean up the input
1256: attr = to_real_attribute_name(name)
1257: raise UnknownAttribute.new(name) if attr.nil?
1258:
1259: if attr == dn_attribute and value.is_a?(String)
1260: value, @base = split_dn_value(value)
1261: end
1262:
1263: logger.debug {"set_attribute(#{name.inspect}, #{value.inspect}): " +
1264: "method maps to #{attr}"}
1265:
1266: # Enforce LDAP-pleasing values
1267: logger.debug {"value = #{value.inspect}, value.class = #{value.class}"}
1268: real_value = value
1269: # Squash empty values
1270: if value.class == Array
1271: real_value = value.collect {|c| (c.nil? or c.empty?) ? [] : c}.flatten
1272: end
1273: real_value = [] if real_value.nil?
1274: real_value = [] if real_value == ''
1275: real_value = [real_value] if real_value.class == String
1276: real_value = [real_value.to_s] if real_value.class == Fixnum
1277: # NOTE: Hashes are allowed for subtyping.
1278:
1279: # Assign the value
1280: @data[attr] = enforce_type(attr, real_value)
1281:
1282: # Return the passed in value
1283: logger.debug {"stub: exiting set_attribute"}
1284: @data[attr]
1285: end
# File lib/active_ldap/base.rb, line 1287
1287: def split_dn_value(value)
1288: dn_value = relative_dn_value = nil
1289: begin
1290: dn_value = DN.parse(value)
1291: rescue DistinguishedNameInvalid
1292: dn_value = DN.parse("#{dn_attribute}=#{value}")
1293: end
1294:
1295: begin
1296: relative_dn_value = dn_value - DN.parse(base_of_class)
1297: relative_dn_value = dn_value if relative_dn_value.rdns.empty?
1298: rescue ArgumentError
1299: relative_dn_value = dn_value
1300: end
1301:
1302: val, *bases = relative_dn_value.rdns
1303: [val.values[0], bases.empty? ? nil : DN.new(*bases).to_s]
1304: end
# File lib/active_ldap/base.rb, line 1105
1105: def to_real_attribute_name(name, allow_normalized_name=false)
1106: ensure_apply_object_class
1107: name = name.to_s
1108: real_name = @attr_methods[name]
1109: real_name ||= @attr_aliases[Inflector.underscore(name)]
1110: if real_name
1111: real_name
1112: elsif allow_normalized_name
1113: @attr_methods[@normalized_attr_names[normalize_attribute_name(name)]]
1114: else
1115: nil
1116: end
1117: end
# File lib/active_ldap/base.rb, line 1500
1500: def update
1501: prepare_data_for_saving do |data, ldap_data|
1502: entries = collect_modified_entries(ldap_data, data)
1503: logger.debug {'#update: traversing data complete'}
1504: logger.debug {"#update: modifying #{dn}"}
1505: self.class.modify(dn, entries)
1506: logger.debug {'#update: modify successful'}
1507: true
1508: end
1509: end