The object in Treequel that wraps an entry. It knows how to construct other branches for the entries below itself, and how to search for those entries.
The default width of LDIF output
The characters to use to fold an LDIF line (newline + a space)
The directory the branch's entry lives in
The DN of the branch.
The DN of the branch.
Create a new Treequel::Branch with the given
directory
, dn
, and an optional
entry
. If the optional entry
object is given, it
will be used to fetch values from the directory; if it isn't provided,
it will be fetched from the directory
the first time it is
needed.
# File lib/treequel/branch.rb, line 79
def initialize( directory, dn, entry=nil )
raise ArgumentError, "nil DN" unless dn
raise ArgumentError, "invalid DN" unless
dn.match( Patterns::DISTINGUISHED_NAME ) || dn.empty?
raise ArgumentError, "can't cast a %s to an LDAP::Entry" % [entry.class.name] unless
entry.nil? || entry.is_a?( Hash )
@directory = directory
@dn = dn
@entry = entry ? stringify_keys( entry ) : nil
@values = {}
@include_operational_attrs = self.class.include_operational_attrs?
self.log.debug "New branch (%s): entry = %p, directory = %p" % [ @dn, @entry, @directory ]
end
Create a new Treequel::Branch from the given
entry
hash from the specified directory
.
# File lib/treequel/branch.rb, line 60
def self::new_from_entry( entry, directory )
entry = Treequel::HashUtilities.stringify_keys( entry )
dnvals = entry.delete( 'dn' ) or
raise ArgumentError, "no 'dn' attribute for entry"
Treequel.logger.debug "Creating Branch from entry: %p in directory: %s" %
[ dnvals.first, directory ]
return self.new( directory, dnvals.first, entry )
end
Addition operator: return a Treequel::BranchCollection that contains
both the receiver and other_branch
.
# File lib/treequel/branch.rb, line 493
def +( other_branch )
return Treequel::BranchCollection.new( self.branchset, other_branch.branchset )
end
Comparable interface: Returns -1 if other_branch is less than, 0 if
other_branch
is equal to, and +1 if other_branch
is greater than the receiving Branch.
# File lib/treequel/branch.rb, line 463
def <=>( other_branch )
# Try the easy cases first
return nil unless other_branch.respond_to?( :dn ) &&
other_branch.respond_to?( :split_dn )
return 0 if other_branch.dn == self.dn
# Try comparing reversed attribute pairs
rval = nil
pairseq = self.split_dn.reverse.zip( other_branch.split_dn.reverse )
pairseq.each do |a,b|
comparison = (a <=> b)
return comparison if !comparison.nil? && comparison.nonzero?
end
# The branches are related, so directly comparing DN strings will work
return self.dn <=> other_branch.dn
end
Fetch the value/s associated with the given attrname
from the
underlying entry.
# File lib/treequel/branch.rb, line 300
def []( attrname )
attrsym = attrname.to_sym
if @values.key?( attrsym )
# self.log.debug " value for %p is cached (%p)." % [ attrname, @values[attrsym] ]
else
self.log.debug " value for %p is NOT cached." % [ attrsym ]
value = self.get_converted_object( attrsym )
self.log.debug " converted value is: %p" % [ value ]
value.freeze if
self.class.freeze_converted_values? &&
value.respond_to?( :freeze )
@values[ attrsym ] = value if value
end
return @values[ attrsym ]
end
Set attribute attrname
to a new value
.
# File lib/treequel/branch.rb, line 331
def []=( attrname, value )
value = [ value ] unless value.is_a?( Array )
value.collect! {|val| self.get_converted_attribute(attrname, val) }
self.log.debug "Modifying %s to %p" % [ attrname, value ]
self.directory.modify( self, attrname.to_s => value )
@values.delete( attrname.to_sym )
self.entry[ attrname.to_s ] = value
end
Return a Treequel::Branchset that will use the receiver as its base.
# File lib/treequel/branch.rb, line 215
def branchset
return Treequel::Branchset.new( self )
end
Return the Branch's immediate children as Treeque::Branch objects.
# File lib/treequel/branch.rb, line 209
def children
return self.search( :one, '(objectClass=*)' )
end
Copy the entry for this Branch to a new entry
with the given newdn
and merge in the specified
attributes
.
# File lib/treequel/branch.rb, line 418
def copy( newdn, attributes={} )
# Fully-qualify RDNs
newdn = newdn + ',' + self.parent_dn unless newdn.index(',')
self.log.debug "Creating a copy of %p at %p" % [ self.dn, newdn ]
newbranch = self.class.new( self.directory, newdn )
attributes = self.entry.merge( stringify_keys(attributes) )
self.log.debug " merged attributes: %p" % [ attributes ]
self.directory.create( newbranch, attributes )
return newbranch
end
Create the entry for this Branch with the
specified attributes
. The attributes
should, at a
minimum, contain the pair :objectClass =>
[:someStructuralObjectClass]
.
groups = dir.ou( :groups ) newgroup = groups.cn( :staff ) newgroup.create( :objectClass => ['posixGroup'], :gidNumber => 2100 ) # => #<Treequel::Branch:0x1086a0ac8 cn=staff,ou=groups,dc=example,dc=com>
# File lib/treequel/branch.rb, line 409
def create( attributes={} )
self.directory.create( self, attributes )
self.clear_caches
return self
end
Delete the specified attributes
, which are the attributes to
delete either as attribute names (in which case all values of the attribute
are deleted), or Hashes of attributes and the Array of value/s which should
be deleted.
# Delete all 'description' attributes branch.delete( :description ) # Delete the 'inetOrgPerson' and 'posixAccount' objectClasses from the entry branch.delete( :objectClass => [:inetOrgPerson, :posixAccount] ) # Delete any blank 'description' or 'cn' attributes: branch.delete( :description => '', :cn => '' )
# File lib/treequel/branch.rb, line 366
def delete( *attributes )
# If no attributes are given, delete the whole entry
if attributes.empty?
self.log.info "No attributes specified; deleting entire entry for %s" % [ self.dn ]
self.directory.delete( self )
# Otherwise, gather up the LDAP::Mod objects that will delete the given attributes
else
self.log.debug "Deleting attributes: %p" % [ attributes ]
mods = attributes.flatten.collect do |attribute|
# Delete particular values of the attribute
if attribute.is_a?( Hash )
attribute.collect do |key,vals|
vals = [ vals ] unless vals.is_a?( Array )
vals.collect! {|val| self.get_converted_attribute(key, val) }
LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, key.to_s, vals )
end
# Delete all values of the attribute
else
LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, attribute.to_s, [] )
end
end
self.directory.modify( self, mods.flatten )
end
self.clear_caches
return true
end
Change the DN the Branch uses to look up its
entry to newdn
.
# File lib/treequel/branch.rb, line 121
def dn=( newdn )
self.clear_caches
@dn = newdn
end
Return the LDAP::Entry associated with the receiver, fetching it from the
directory if necessary. Returns nil
if the entry doesn't
exist in the directory.
# File lib/treequel/branch.rb, line 145
def entry
@entry ||= self.lookup_entry
end
Comparison-by-value method – returns true
if the receiver has
the same DN as other
.
# File lib/treequel/branch.rb, line 449
def eql?( other )
return false unless other.class.eql?( self.class )
return self.hash == other.hash
end
Returns true
if there is an entry currently in the directory
with the branch's DN.
# File lib/treequel/branch.rb, line 152
def exists?
return self.entry ? true : false
end
Fetch a new Treequel::Branch object for the child
of the receiver with the specified rdn
.
# File lib/treequel/branch.rb, line 484
def get_child( rdn )
self.log.debug "Getting child %p from base = %p" % [ rdn, self.dn ]
newdn = [ rdn, self.dn ].reject {|part| part.empty? }.join( ',' )
return self.class.new( self.directory, newdn )
end
Generates a Fixnum hash for the receiver.
# File lib/treequel/branch.rb, line 456
def hash
return [ self.class, self.dn ].hash
end
Enable (if new_setting
is true) or disable fetching of
operational attributes (RC4512, section 3.4).
# File lib/treequel/branch.rb, line 129
def include_operational_attrs=( new_setting )
self.clear_caches
@include_operational_attrs = new_setting ? true : false
end
Returns a human-readable representation of the object suitable for debugging.
# File lib/treequel/branch.rb, line 222
def inspect
return "#<%s:0x%0x %s @ %s entry=%p>" % [
self.class.name,
self.object_id * 2,
self.dn,
self.directory,
@entry,
]
end
Returns true
if the Branch's entry has been fetched from
the directory.
# File lib/treequel/branch.rb, line 158
def loaded?
return @entry ? true : false
end
Return Treequel::Schema::AttributeType
instances for each of the receiver's objectClass's MAY
attributeTypes. If any additional_object_classes
are given,
include the MAY attributeTypes for them as well. This can be used to
predict what optional attributes could be added to the entry if the
additional_object_classes
were added to it.
# File lib/treequel/branch.rb, line 588
def may_attribute_types( *additional_object_classes )
return self.object_classes( *additional_object_classes ).
collect {|oc| oc.may }.flatten.uniq
end
Return a Hash of the optional attributes allowed by the Branch's
objectClasses. If any additional_object_classes
are given,
include the attributes that would be available for the entry if it had
them.
# File lib/treequel/branch.rb, line 608
def may_attributes_hash( *additional_object_classes )
entry = self.entry
attrhash = {}
self.may_attribute_types( *additional_object_classes ).each do |attrtype|
# self.log.debug " adding attrtype %p to the MAY attributes hash" % [ attrtype.named ]
if attrtype.single?
attrhash[ attrtype.name ] = nil
else
attrhash[ attrtype.name ] = []
end
end
# :FIXME: Does the resulting hash need the additional objectClasses? objectClass is
# MUST via 'top', so it should already exist in that hash when merged with
# this one...
# attrhash[ :objectClass ] |= additional_object_classes
return attrhash
end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of
the receiver's objectClass's MAY attributeTypes. If any
additional_object_classes
are given, include the OIDs of the
MAY attributes for them as well. This can be used to predict what optional
attributes could be added to the entry if the
additional_object_classes
were added to it.
# File lib/treequel/branch.rb, line 599
def may_oids( *additional_object_classes )
return self.object_classes( *additional_object_classes ).
collect {|oc| oc.may_oids }.flatten.uniq
end
Make the changes to the entry specified by the given
attributes
.
branch.merge( :description => ['The syadmin group'], :cn => ['sysadmin'] )
# File lib/treequel/branch.rb, line 344
def merge( attributes )
self.directory.modify( self, attributes )
self.clear_caches
return true
end
Move the entry associated with this branch to a new entry indicated by
rdn
. If any attributes
are given, also replace
the corresponding attributes on the new entry with them.
# File lib/treequel/branch.rb, line 438
def move( rdn )
self.log.debug "Asking the directory to move me to an entry called %p" % [ rdn ]
self.directory.move( self, rdn )
self.clear_caches
return self
end
Return Treequel::Schema::AttributeType
instances for each of the receiver's objectClass's MUST
attributeTypes. If any additional_object_classes
are given,
include the MUST attributeTypes for them as well. This can be used to
predict what attributes would need to be present for the entry to be saved
if it added the additional_object_classes
to its own.
# File lib/treequel/branch.rb, line 542
def must_attribute_types( *additional_object_classes )
oclasses = self.object_classes( *additional_object_classes )
types = oclasses.map( &:must ).flatten.uniq
return types
end
Return a Hash of the attributes required by the Branch's objectClasses.
If any additional_object_classes
are given, include the
attributes that would be necessary for the entry to be saved with them.
# File lib/treequel/branch.rb, line 564
def must_attributes_hash( *additional_object_classes )
attrhash = {}
self.must_attribute_types( *additional_object_classes ).each do |attrtype|
# self.log.debug " adding attrtype %p to the MUST attributes hash" % [ attrtype.name ]
if attrtype.name == :objectClass
attrhash[ :objectClass ] = ['top'] | additional_object_classes
elsif attrtype.single?
attrhash[ attrtype.name ] = ''
else
attrhash[ attrtype.name ] = ['']
end
end
return attrhash
end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of
the receiver's objectClass's MUST attributeTypes. If any
additional_object_classes
are given, include the OIDs of the
MUST attributes for them as well. This can be used to predict what
attributes would need to be present for the entry to be saved if it added
the additional_object_classes
to its own.
# File lib/treequel/branch.rb, line 555
def must_oids( *additional_object_classes )
return self.object_classes( *additional_object_classes ).
collect {|oc| oc.must_oids }.flatten.uniq.reject {|val| val == '' }
end
Return Treequel::Schema::ObjectClass
instances for each of the receiver's objectClass attributes. If any
additional_classes
are given, merge them with the current list
of the current objectClasses for the lookup.
# File lib/treequel/branch.rb, line 501
def object_classes( *additional_classes )
# self.log.debug "Fetching object classes for %s" % [ self.dn ]
schema = self.directory.schema
oc_oids = self[:objectClass] || []
oc_oids |= additional_classes.collect {|str| str.to_sym }
oc_oids << 'top' if oc_oids.empty?
oclasses = []
oc_oids.each do |oid|
oc = schema.object_classes[ oid.to_sym ] or
raise Treequel::Error, "schema doesn't have a %p objectClass" % [ oid ]
oclasses << oc
end
# self.log.debug " found %d objectClasses: %p" % [ oclasses.length, oclasses.map(&:name) ]
return oclasses.uniq
end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's operational attributes.
# File lib/treequel/branch.rb, line 529
def operational_attribute_oids
return self.operational_attribute_types.inject([]) do |oids, attrtype|
oids.push( *attrtype.names )
oids << attrtype.oid
end
end
Return the receiver's operational attributes as attributeType schema objects.
# File lib/treequel/branch.rb, line 522
def operational_attribute_types
return self.directory.schema.operational_attribute_types
end
Return the Branch's immediate parent node.
# File lib/treequel/branch.rb, line 194
def parent
pardn = self.parent_dn or return nil
return self.class.new( self.directory, pardn )
end
Return the DN of this entry's parent, or nil if it doesn't have one.
# File lib/treequel/branch.rb, line 186
def parent_dn
return nil if self.dn == self.directory.base_dn
return '' if self.dn.index( ',' ).nil?
return self.split_dn( 2 ).last
end
Return the RDN of the branch.
# File lib/treequel/branch.rb, line 164
def rdn
return self.split_dn( 2 ).first
end
Return the attribute/s which make up this Branch's RDN as a Hash.
# File lib/treequel/branch.rb, line 137
def rdn_attributes
return make_rdn_hash( self.rdn )
end
Perform a search with the specified scope
,
filter
, and parameters
using the receiver as the
base. See Trequel::Directory#search for details. Returns an Array of Treequel::Branch objects.
# File lib/treequel/branch.rb, line 203
def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )
return self.directory.search( self, scope, filter, parameters, &block )
end
Return the receiver's DN as an Array of attribute=value pairs. If the
optional limit
is non-zero, only the limit-1
first pairs are split from the DN, and the remainder will be returned as
the last element.
# File lib/treequel/branch.rb, line 172
def split_dn( limit=0 )
return self.dn.split( /\s*,\s*/, limit )
end
Return the Branch as a Hash.
# File lib/treequel/branch.rb, line 284
def to_hash
entry = self.entry || self.valid_attributes_hash
self.log.debug " making a Hash from an entry: %p" % [ entry ]
return entry.keys.inject({}) do |hash, attribute|
if attribute == 'dn'
hash[ attribute ] = self.dn
else
hash[ attribute ] = self[ attribute ]
end
hash
end
end
Return the Branch as an LDAP::LDIF::Entry.
# File lib/treequel/branch.rb, line 267
def to_ldif( width=DEFAULT_LDIF_WIDTH )
ldif = "dn: %s\n" % [ self.dn ]
entry = self.entry || self.valid_attributes_hash
self.log.debug " making LDIF from an entry: %p" % [ entry ]
entry.keys.reject {|k| k == 'dn' }.each do |attribute|
Array( entry[attribute] ).each do |val|
ldif << ldif_for_attr( attribute, val, width )
end
end
return LDAP::LDIF::Entry.new( ldif )
end
Return the entry's DN as an RFC1781-style UFN (User-Friendly Name).
# File lib/treequel/branch.rb, line 234
def to_ufn
if LDAP.respond_to?( :dn2ufn )
return LDAP.dn2ufn( self.dn )
# An implementation for LDAP libraries with no
# dn2ufn
else
ufn = ''
tuples = self.split_dn
# Separate the trailing domainComponents
dcs = []
dcs << tuples.pop while tuples.last =~ /^dc\s*=/i
# Append the non-dc tuples with their attributes stripped first
ufn << tuples.collect do |rdn|
rdn.
gsub(/\b#{ATTRIBUTE_TYPE}\s*=/, '').
gsub(/\s*\+\s*/, ' + ')
end.join( ', ' )
# Now append the DCs joined with dots
unless dcs.empty?
ufn << ', '
ufn << dcs.reverse.map {|rdn| rdn.sub(/dc\s*=\s*/i, '') }.join( '.' )
end
return ufn
end
end
Return true
if the specified attrname
is a valid
attributeType given the receiver's current objectClasses. Does not
include operational attributes.
# File lib/treequel/branch.rb, line 659
def valid_attribute?( attroid )
return !self.valid_attribute_type( attroid ).nil?
end
Return a uniqified Array of OIDs (numeric OIDs as Strings, named OIDs as Symbols) for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.
# File lib/treequel/branch.rb, line 643
def valid_attribute_oids
return self.must_oids | self.may_oids
end
If the attribute associated with the given attroid
is in the
list of valid attributeTypes for the receiver given its objectClasses,
return the AttributeType object that corresponds with it. If it isn't
valid, return nil. Includes operational attributes.
# File lib/treequel/branch.rb, line 652
def valid_attribute_type( attroid )
return self.valid_attribute_types.find {|attr_type| attr_type.valid_name?(attroid) }
end
Return Treequel::Schema::AttributeType instances for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.
# File lib/treequel/branch.rb, line 633
def valid_attribute_types
return self.must_attribute_types |
self.may_attribute_types |
self.operational_attribute_types
end
Return a Hash of all the attributes allowed by the Branch's
objectClasses. If any additional_object_classes
are given,
include the attributes that would be available for the entry if it had
them.
# File lib/treequel/branch.rb, line 667
def valid_attributes_hash( *additional_object_classes )
self.log.debug "Gathering a hash of all valid attributes:"
must = self.must_attributes_hash( *additional_object_classes )
self.log.debug " MUST attributes: %p" % [ must ]
may = self.may_attributes_hash( *additional_object_classes )
self.log.debug " MAY attributes: %p" % [ may ]
return may.merge( must )
end
Fetch one or more values for the specified attributes
from the
entry.
branch.values_at( :cn, :objectClass ) => [[“sysadmin”], [“top”, “posixGroup”, “apple-group”]]
# File lib/treequel/branch.rb, line 323
def values_at( *attributes )
return attributes.collect do |attribute|
self[ attribute ]
end
end
Clear any cached values when the structural state of the object changes.
# File lib/treequel/branch.rb, line 778
def clear_caches
self.log.debug "Clearing entry and values caches."
@entry = nil
@values.clear
end
Convert the specified object
according to the Branch's
directory's conversion rules, and return it.
# File lib/treequel/branch.rb, line 765
def get_converted_attribute( attrsym, object )
if attribute = self.directory.schema.attribute_types[ attrsym ]
self.log.debug "converting %p object (a %p) to a %s attribute" %
[ attrsym, object.class, attribute.syntax.desc ]
return self.directory.convert_to_attribute( attribute.syntax_oid, object )
else
self.log.info "no attributeType for %p" % [ attrsym ]
return object.to_s
end
end
Get the value associated with attrsym
, convert it to a Ruby
object if the Branch's directory has a conversion rule, and return it.
# File lib/treequel/branch.rb, line 741
def get_converted_object( attrsym )
value = self.entry ? self.entry[ attrsym.to_s ] : nil
if attribute = self.directory.schema.attribute_types[ attrsym ]
syntax = attribute.syntax
syntax_oid = syntax.oid if syntax
if attribute.single?
value = self.directory.convert_to_object( syntax_oid, value.first ) if value
else
value = Array( value ).collect do |raw|
self.directory.convert_to_object( syntax_oid, raw )
end
end
else
self.log.info "no attributeType for %p" % [ attrsym ]
end
return value
end
Fetch the entry from the Branch's directory.
# File lib/treequel/branch.rb, line 721
def lookup_entry
self.log.debug "Looking up entry for %s" % [ self.dn ]
entry = nil
if self.include_operational_attrs?
self.log.debug " including operational attributes."
entry = self.directory.get_extended_entry( self )
else
self.log.debug " not including operational attributes."
entry = self.directory.get_entry( self )
end
entry.delete( 'dn' ) if entry
self.log.debug " entry is: %p" % [ entry ]
return entry
end
Proxy method: call traverse_branch if
attribute
is a valid attribute and value
isn't nil
.
# File lib/treequel/branch.rb, line 684
def method_missing( attribute, value=nil, additional_attributes={} )
return super( attribute ) if value.nil?
return self.traverse_branch( attribute, value, additional_attributes )
end
If attribute
matches a valid attribute type in the
directory's schema, return a new Branch for
the RDN of attribute
and value
and
additional_attributes
(if it's a multi-value RDN).
# (Called via #method_missing) branch = Treequel::Branch.new( directory, 'ou=people,dc=acme,dc=com' ) branch.uid( :chester ).dn # => 'uid=chester,ou=people,dc=acme,dc=com' branch.uid( :chester, :employeeType => 'admin' ).dn # => 'uid=chester+employeeType=admin,ou=people,dc=acme,dc=com'
Raises a NoMethodError if the attribute
or any
additional_attributes
are not valid attributeTypes.
# File lib/treequel/branch.rb, line 703
def traverse_branch( attribute, value, additional_attributes={} )
valid_types = self.directory.schema.attribute_types
# Raise if either the primary attribute or any secondary attributes are invalid
if !valid_types.key?( attribute )
raise NoMethodError, "undefined method `%s' for %p" % [ attribute, self ]
elsif invalid = additional_attributes.keys.find {|ex_attr| !valid_types.key?(ex_attr) }
raise NoMethodError, "invalid secondary attribute `%s' for %p" %
[ invalid, self ]
end
# Make a normalized RDN from the arguments and return the Branch for it
rdn = rdn_from_pair_and_hash( attribute, value, additional_attributes )
return self.get_child( rdn )
end