require 'ldap' require 'ldap/schema' module LDAP class Schema2 < Schema @@attr_cache = {} @@class_cache = {} # attr # # This is just like LDAP::Schema#attr except that it allows # look up in any of the given keys. # e.g. # attr('attributeTypes', 'cn', 'DESC') # attr('ldapSyntaxes', '1.3.6.1.4.1.1466.115.121.1.5', 'DESC') def attr(sub, type, at) return '' if sub.empty? return '' if type.empty? return '' if at.empty? # Check already parsed options first if @@attr_cache.has_key? sub \ and @@attr_cache[sub].has_key? type \ and @@attr_cache[sub][type].has_key? at return @@attr_cache[sub][type][at].dup end # Initialize anything that is required unless @@attr_cache.has_key? sub @@attr_cache[sub] = {} end unless @@attr_cache[sub].has_key? type @@attr_cache[sub][type] = {} end at = at.upcase self[sub].each do |s| line = '' if type[0..0] =~ /[0-9]/ line = s if s =~ /\(\s+#{type}\s+(?:[A-Z]|\))/ else line = s if s =~ /NAME\s+\(?.*'#{type}'.*\)?\s+(?:[A-Z]|\))/ end # I need to check, but I think some of these matchs # overlap. I'll need to check these when I'm less sleepy. multi = '' case line when /#{at}\s+[\)A-Z]/ @@attr_cache[sub][type][at] = ['TRUE'] return ['TRUE'] when /#{at}\s+'(.+?)'/ @@attr_cache[sub][type][at] = [$1] return [$1] when /#{at}\s+\((.+?)\)/ multi = $1 when /#{at}\s+\(([\w\d\s\.]+)\)/ multi = $1 when /#{at}\s+([\w\d\.]+)/ @@attr_cache[sub][type][at] = [$1] return [$1] end # Split up multiple matches # if oc then it is sep'd by $ # if attr then bu spaces if multi.match(/\$/) @@attr_cache[sub][type][at] = multi.split("$").collect{|attr| attr.strip} return @@attr_cache[sub][type][at].dup elsif not multi.empty? @@attr_cache[sub][type][at] = multi.gsub(/'/, '').split(' ').collect{|attr| attr.strip} return @@attr_cache[sub][type][at].dup end end @@attr_cache[sub][type][at] = [] return [] end # attribute_aliases # # Returns all names from the LDAP schema for the # attribute given. def attribute_aliases(attr) attr('attributeTypes', attr, 'NAME') end # attribute aliases # read_only? # # Returns true if an attribute is read-only # NO-USER-MODIFICATION def read_only?(attr) result = attr('attributeTypes', attr, 'NO-USER-MODIFICATION') return true if result[0] == 'TRUE' return false end # single_value? # # Returns true if an attribute can only have one # value defined # SINGLE-VALUE def single_value?(attr) result = attr('attributeTypes', attr, 'SINGLE-VALUE') return true if result[0] == 'TRUE' return false end # binary? # # Returns true if the given attribute's syntax # is X-NOT-HUMAN-READABLE or X-BINARY-TRANSFER-REQUIRED def binary?(attr) # Get syntax OID syntax = attr('attributeTypes', attr, 'SYNTAX') return false if syntax.empty? # This seems to indicate binary result = attr('ldapSyntaxes', syntax[0], 'X-NOT-HUMAN-READABLE') return true if result[0] == "TRUE" # Get if binary transfer is required (non-binary types) # Usually these have the above tag result = attr('ldapSyntaxes', syntax[0], 'X-BINARY-TRANSFER-REQUIRED') return true if result[0] == "TRUE" return false end # binary? # binary_required? # # Returns true if the value MUST be transferred in binary def binary_required?(attr) # Get syntax OID syntax = attr('attributeTypes', attr, 'SYNTAX') return false if syntax.empty? # Get if binary transfer is required (non-binary types) # Usually these have the above tag result = attr('ldapSyntaxes', syntax[0], 'X-BINARY-TRANSFER-REQUIRED') return true if result[0] == "TRUE" return false end # binary_required? # class_attributes # # Returns an Array of all the valid attributes (but not with full aliases) # for the given objectClass def class_attributes(objc) if @@class_cache.has_key? objc return @@class_cache[objc] end # Setup the cache @@class_cache[objc] = {} # First get all the current level attributes @@class_cache[objc] = {:must => attr('objectClasses', objc, 'MUST'), :may => attr('objectClasses', objc, 'MAY')} # Now add all attributes from the parent object (SUPerclasses) # Hopefully an iterative approach will be pretty speedy # 1. build complete list of SUPs # 2. Add attributes from each sups = attr('objectClasses', objc, 'SUP') loop do start_size = sups.size new_sups = [] sups.each do |sup| new_sups += attr('objectClasses', sup, 'SUP') end sups += new_sups sups.uniq! break if sups.size == start_size end sups.each do |sup| @@class_cache[objc][:must] += attr('objectClasses', sup, 'MUST') @@class_cache[objc][:may] += attr('objectClasses', sup, 'MAY') end # Clean out the dupes. @@class_cache[objc][:must].uniq! @@class_cache[objc][:may].uniq! # Return the cached value return @@class_cache[objc].dup end end # Schema2 class Conn def schema(base = nil, attrs = nil, sec = 0, usec = 0) attrs ||= [ 'objectClasses', 'attributeTypes', 'matchingRules', 'matchingRuleUse', 'dITStructureRules', 'dITContentRules', 'nameForms', 'ldapSyntaxes', ] base ||= root_dse(['subschemaSubentry'], sec, usec)[0]['subschemaSubentry'][0] base ||= 'cn=schema' ent = search2(base, LDAP_SCOPE_BASE, '(objectClass=subschema)', attrs, false, sec, usec) return Schema2.new(ent[0]) end end end # end LDAP