Source code for ldaptor.protocols.ldap.distinguishedname

from functools import total_ordering


from ldaptor._encoder import to_unicode, TextStrAlias

# See rfc2253
# Note that RFC 2253 sections 2.4 and 3 disagree whether "=" needs to
# be quoted. Let's trust the syntax, slapd refuses to accept unescaped
# "=" in RDN values.
escapedChars = ',+"\\<>;='
escapedChars_leading = " #"
escapedChars_trailing = " #"


[docs]def escape(s): r = "" r_trailer = "" if s and s[0] in escapedChars_leading: r = "\\" + s[0] s = s[1:] if s and s[-1] in escapedChars_trailing: r_trailer = "\\" + s[-1] s = s[:-1] for c in s: if c in escapedChars: r = r + "\\" + c elif ord(c) <= 31: r = r + "\\%02X" % ord(c) else: r = r + c return r + r_trailer
[docs]def unescape(s): r = "" while s: if s[0] == "\\": if s[1] in "0123456789abcdef": r = r + chr(int(s[1:3], 16)) s = s[3:] else: r = r + s[1] s = s[2:] else: r = r + s[0] s = s[1:] return r
def _splitOnNotEscaped(s, separator): if not s: return [] r = [""] while s: first = s[0:1] if first == "\\": r[-1] = r[-1] + s[:2] s = s[2:] else: if first == separator: r.append("") s = s[1:] while s[0:1] == " ": s = s[1:] else: r[-1] = r[-1] + first s = s[1:] return r
[docs]class InvalidRelativeDistinguishedName(Exception): """ Invalid relative distinguished name. It is assumed that passed RDN is of str type: bytes for PY2 and unicode for PY3. """ def __init__(self, rdn): Exception.__init__(self) self.rdn = rdn def __str__(self): return "Invalid relative distinguished name %s." % repr(self.rdn)
[docs]class LDAPAttributeTypeAndValue(TextStrAlias): # TODO I should be used everywhere attributeType = None value = None def __init__(self, stringValue=None, attributeType=None, value=None): if stringValue is None: assert attributeType is not None assert value is not None self.attributeType = to_unicode(attributeType) self.value = to_unicode(value) else: assert attributeType is None assert value is None stringValue = to_unicode(stringValue) if "=" not in stringValue: raise InvalidRelativeDistinguishedName(stringValue) self.attributeType, self.value = stringValue.split("=", 1)
[docs] def getText(self): return "=".join((escape(self.attributeType), escape(self.value)))
def __repr__(self): return ( self.__class__.__name__ + "(attributeType=" + repr(self.attributeType) + ", value=" + repr(self.value) + ")" ) def __hash__(self): return hash((self.attributeType, self.value)) def __eq__(self, other): if not isinstance(other, LDAPAttributeTypeAndValue): return NotImplemented return ( self.attributeType.lower() == other.attributeType.lower() and self.value.lower() == other.value.lower() ) def __ne__(self, other): return not (self == other) def __lt__(self, other): if not isinstance(other, self.__class__): return False if self.attributeType != other.attributeType: return self.attributeType < other.attributeType else: return self.value < other.value def __gt__(self, other): return self != other and self > other def __le__(self, other): return not self > other def __ge__(self, other): return not self < other
[docs]class RelativeDistinguishedName(TextStrAlias): """LDAP Relative Distinguished Name.""" attributeTypesAndValues = None def __init__(self, magic=None, stringValue=None, attributeTypesAndValues=None): if magic is not None: assert stringValue is None assert attributeTypesAndValues is None if isinstance(magic, RelativeDistinguishedName): attributeTypesAndValues = magic.split() elif isinstance(magic, (bytes, str)): stringValue = magic else: attributeTypesAndValues = magic if stringValue is None: assert attributeTypesAndValues is not None assert not isinstance(attributeTypesAndValues, (bytes, str)) self.attributeTypesAndValues = tuple(attributeTypesAndValues) else: assert attributeTypesAndValues is None self.attributeTypesAndValues = tuple( LDAPAttributeTypeAndValue(stringValue=unescape(x)) for x in _splitOnNotEscaped(to_unicode(stringValue), "+") )
[docs] def split(self): return self.attributeTypesAndValues
[docs] def getText(self): return "+".join([x.getText() for x in self.attributeTypesAndValues])
def __repr__(self): return ( self.__class__.__name__ + "(attributeTypesAndValues=" + repr(self.attributeTypesAndValues) + ")" ) def __hash__(self): return hash(self.attributeTypesAndValues) def __eq__(self, other): if not isinstance(other, RelativeDistinguishedName): return NotImplemented return self.split() == other.split() def __ne__(self, other): return not (self == other) def __lt__(self, other): if not isinstance(other, self.__class__): return False return self.split() < other.split() def __gt__(self, other): return self != other and self >= other def __le__(self, other): return not self > other def __ge__(self, other): return not self < other
[docs] def count(self): return len(self.attributeTypesAndValues)
[docs]@total_ordering class DistinguishedName(TextStrAlias): """LDAP Distinguished Name.""" listOfRDNs = None def __init__(self, magic=None, stringValue=None, listOfRDNs=None): assert magic is not None or stringValue is not None or listOfRDNs is not None if magic is not None: assert stringValue is None assert listOfRDNs is None if isinstance(magic, DistinguishedName): listOfRDNs = magic.split() elif isinstance(magic, (bytes, str)): # This might need to be expended if we want to support # different encodings. stringValue = magic else: listOfRDNs = magic if stringValue is None: assert listOfRDNs is not None for x in listOfRDNs: assert isinstance(x, RelativeDistinguishedName) self.listOfRDNs = tuple(listOfRDNs) else: assert listOfRDNs is None self.listOfRDNs = tuple( RelativeDistinguishedName(stringValue=x) for x in _splitOnNotEscaped(to_unicode(stringValue), ",") )
[docs] def split(self): return self.listOfRDNs
[docs] def up(self): return DistinguishedName(listOfRDNs=self.listOfRDNs[1:])
[docs] def getText(self): return ",".join([x.getText() for x in self.listOfRDNs])
def __repr__(self): return self.__class__.__name__ + "(listOfRDNs=" + repr(self.listOfRDNs) + ")" def __hash__(self): return hash(self.getText()) def __eq__(self, other): if isinstance(other, bytes): return self.getText().encode("utf-8") == other if isinstance(other, str): return self.getText() == other if not isinstance(other, DistinguishedName): return NotImplemented return self.split() == other.split() def __ne__(self, other): return not (self == other) def __lt__(self, other): """ Comparison used for determining the hierarchy. """ if not isinstance(other, DistinguishedName): return NotImplemented # The comparison is naive and broken. # See https://github.com/twisted/ldaptor/issues/94 return self.split() < other.split()
[docs] def getDomainName(self): domainParts = [] l = list(self.listOfRDNs) l.reverse() for rdn in l: if rdn.count() != 1: break attributeTypeAndValue = rdn.split()[0] if attributeTypeAndValue.attributeType.upper() != "DC": break domainParts.insert(0, attributeTypeAndValue.value) if domainParts: return ".".join(domainParts) else: return None
[docs] def contains(self, other): """Does the tree rooted at DN contain or equal the other DN.""" if self == other: return 1 if not isinstance(other, DistinguishedName): other = DistinguishedName(other) its = list(other.split()) mine = list(self.split()) while mine and its: m = mine.pop() i = its.pop() if m != i: return 0 if mine: return 0 return 1