Source code for tldap.dn

"""
dn.py - misc stuff for handling distinguished names (see RFC 4514)
"""
import six

import tldap.exceptions


[docs] def escape_dn_chars(s): """ Escape all DN special characters found in s with a back-slash (see RFC 4514, section 2.4) """ if s: assert isinstance(s, six.string_types) s = s.replace('\\', '\\\\') s = s.replace(',', '\\,') s = s.replace('+', '\\+') s = s.replace('"', '\\"') s = s.replace('<', '\\<') s = s.replace('>', '\\>') s = s.replace(';', '\\;') s = s.replace('=', '\\=') s = s.replace('\000', '\\\000') if s[0] == '#' or s[0] == ' ': s = ''.join(('\\', s)) if s[-1] == ' ': s = ''.join((s[:-1], '\\ ')) return s
def _whitespace(value, i): while True: if i >= len(value): break if not _isSPACE(value[i]): break i = i + 1 return ("", i) # --- RFC4512: SINGLE CHARACTERS --- def _isALPHA(char): assert len(char) == 1 return (char >= 'A' and char <= 'Z') or (char >= 'a' and char <= 'z') def _isleadkeychar(char): assert len(char) == 1 return _isALPHA(char) def _iskeychar(char): assert len(char) == 1 return _isALPHA(char) or _isDIGIT(char) or char == "-" def _isDIGIT(char): assert len(char) == 1 return char >= '0' and char <= '9' def _isHEX(char): assert len(char) == 1 return (char >= '0' and char <= '9') \ or (char >= 'A' and char <= 'F') \ or (char >= 'a' and char <= 'f') def _isSPACE(char): assert len(char) == 1 return char == ' ' def _isDQUOTE(char): assert len(char) == 1 return char == '"' def _isSHARP(char): assert len(char) == 1 return char == '#' def _isPLUS(char): assert len(char) == 1 return char == '+' def _isCOMMA(char): assert len(char) == 1 return char == ',' def _isDOT(char): assert len(char) == 1 return char == "." def _isSEMI(char): assert len(char) == 1 return char == ";" def _isLANGLE(char): assert len(char) == 1 return char == "<" def _isEQUALS(char): assert len(char) == 1 return char == "=" def _isRANGLE(char): assert len(char) == 1 return char == ">" def _isESC(char): assert len(char) == 1 return char == "\\" # --- RFC4512: STRINGS --- def _keystring(value, i): start = i if i >= len(value): return (None, start) if not _isleadkeychar(value[i]): return (None, start) i = i + 1 while i < len(value) and _iskeychar(value[i]): i = i + 1 return (value[start:i], i) def _number(value, i): start = i if i >= len(value): return (None, start) if not _isDIGIT(value[i]): return (None, start) if value[i] == "0": i = i + 1 return (value[start:i], i) while i < len(value) and _isDIGIT(value[i]): i = i + 1 return (value[start:i], i) def _numericoid(value, i): start = i while True: (number, i) = _number(value, i) if number is None: return (None, i) if i >= len(value): return (value[start:i], i) if value[i] != ".": return (value[start:i], i) i = i + 1 def _descr(value, i): return _keystring(value, i) def _UTFMB(value, i): if ord(value[i]) >= 0x80: return (value[i], i + 1) return (None, i) # --- RFC 4514: CHARACTERS --- def _isspecial(value): return _isescaped(value) or _isSPACE(value) \ or _isSHARP(value) or _isEQUALS(value) def _isescaped(value): return _isDQUOTE(value) or _isPLUS(value) or _isCOMMA(value) \ or _isSEMI(value) or _isLANGLE(value) or _isRANGLE(value) def _isLUTF1(char): # 0x20 space not allowed # 0x21 ! allowed # 0x22 : not allowed # 0x23 '#' not allowed assert len(char) == 1 n = ord(char) return (n >= 0x01 and n <= 0x1F) \ or (n == 0x21) \ or (n >= 0x24 and n <= 0x2A) \ or (n >= 0x2D and n <= 0x3A) \ or (n == 0x3D) \ or (n >= 0x3F and n <= 0x5B) \ or (n >= 0x5D and n <= 0x7F) def _isTUTF1(char): # 0x20 space not allowed # 0x21 ! allowed # 0x22 : not allowed # 0x23 '#' allowed assert len(char) == 1 n = ord(char) return (n >= 0x01 and n <= 0x1F) \ or (n == 0x21) \ or (n >= 0x23 and n <= 0x2A) \ or (n >= 0x2D and n <= 0x3A) \ or (n == 0x3D) \ or (n >= 0x3F and n <= 0x5B) \ or (n >= 0x5D and n <= 0x7F) def _isSUTF1(char): # 0x20 space allowed # 0x21 ! allowed # 0x22 : not allowed # 0x23 '#' allowed assert len(char) == 1 n = ord(char) return (n >= 0x01 and n <= 0x21) \ or (n >= 0x23 and n <= 0x2A) \ or (n >= 0x2D and n <= 0x3A) \ or (n == 0x3D) \ or (n >= 0x3F and n <= 0x5B) \ or (n >= 0x5D and n <= 0x7F) # --- RFC 4514: STRINGS --- def _leadchar(value, i): start = i if i >= len(value): return (None, start) if _isLUTF1(value[i]): return (value[i], i + 1) (utfmb, i) = _UTFMB(value, i) if utfmb is not None: return (utfmb, i) return (None, start) def _trailchar(value, i): start = i if i >= len(value): return (None, start) if _isTUTF1(value[i]): return (value[i], i + 1) (utfmb, i) = _UTFMB(value, i) if utfmb is not None: return (utfmb, i) return (None, start) def _stringchar(value, i): start = i if i >= len(value): return (None, start) if _isSUTF1(value[i]): return (value[i], i + 1) (utfmb, i) = _UTFMB(value, i) if utfmb is not None: return (utfmb, i) return (None, start) def _distinguishedName(value, i): start = i result = [] (relativeDistinguishedName, i) = _relativeDistinguishedName(value, i) if relativeDistinguishedName is None: return (None, start) result.append(relativeDistinguishedName) while True: # whitespace not allowed by RFC4514 before comma, however is allowed # for backword compatability. _, i = _whitespace(value, i) if i >= len(value): return (result, i) if not _isCOMMA(value[i]): return (result, i) i = i + 1 # whitespace not allowed by RFC4514 after comma, however is allowed for # backword compatability. _, i = _whitespace(value, i) (relativeDistinguishedName, i) = _relativeDistinguishedName(value, i) if relativeDistinguishedName is None: return (None, start) result.append(relativeDistinguishedName) def _relativeDistinguishedName(value, i): start = i result = [] (attributeTypeAndValue, i) = _attributeTypeAndValue(value, i) if attributeTypeAndValue is None: return (None, start) result.append(attributeTypeAndValue) while True: if i >= len(value): return (result, i) if not _isPLUS(value[i]): return (result, i) i = i + 1 (attributeTypeAndValue, i) = _attributeTypeAndValue(value, i) if attributeTypeAndValue is None: return (None, start) result.append(attributeTypeAndValue) def _attributeTypeAndValue(value, i): (attributeType, i) = _attributeType(value, i) if attributeType is None: return (None, i) if not _isEQUALS(value[i]): return (None, i) i = i + 1 (attributeValue, i) = _attributeValue(value, i) if attributeValue is None: return (None, i) return ((attributeType, attributeValue, 1), i) def _attributeType(value, i): (descr, i) = _keystring(value, i) if descr is not None: return (descr, i) (numericoid, i) = _numericoid(value, i) if numericoid is not None: return (numericoid, i) return (None, i) def _attributeValue(value, i): start = i (string, i) = _string(value, i) if string is not None: return (string, i) (string, i) = _hexstring(value, i) if string is not None: return (string, i) return (None, start) def _string(value, i): start = i result = "" if i >= len(value): return (None, start) (leadchar, i) = _leadchar(value, i) if leadchar is not None: result += leadchar else: (pair, i) = _pair(value, i) if pair is None: return (None, start) result += pair prev_result = None trail_i = i while i < len(value): this_i = i (stringchar, i) = _stringchar(value, i) if stringchar is not None: this_result = stringchar else: (pair, i) = _pair(value, i) if pair is None: break this_result = pair if prev_result is not None: result += prev_result prev_result = this_result trail_i = this_i i = trail_i if i >= len(value): return (None, start) (trailchar, i) = _trailchar(value, i) if trailchar is not None: result += trailchar else: (pair, i) = _pair(value, i) if pair is None: return (None, start) result += pair return (result, i) def _pair(value, i): start = i if i >= len(value): return (None, start) if not _isESC(value[i]): return (None, start) i = i + 1 if i >= len(value): return (None, start) if _isESC(value[i]) or _isspecial(value[i]): return (value[i], i + 1) (hexpair, i) = _hexpair(value, i) if hexpair is not None: return (hexpair, i) return (None, start) def _hexstring(value, i): start = i result = "" if i >= len(value): return (None, start) if not _isSHARP(value[i]): return (None, start) i = i + 1 (hexpair, i) = _hexpair(value, i) if hexpair is None: return (None, start) result += hexpair while True: (hexpair, i) = _hexpair(value, i) if hexpair is None: return (result, i) result += hexpair def _hexpair(value, i): start = i if i >= len(value): return (None, start) if not _isHEX(value[i]): return (None, start) i = i + 1 if i >= len(value): return (None, start) if not _isHEX(value[i]): return (None, start) i = i + 1 return (chr(int(value[start:i], 16)), i)
[docs] def str2dn(dn, flags=0): """ This function takes a DN as string as parameter and returns a decomposed DN. It's the inverse to dn2str(). flags describes the format of the dn See also the OpenLDAP man-page ldap_str2dn(3) """ # if python2, we need unicode string if not isinstance(dn, six.text_type): dn = dn.decode("utf_8") assert flags == 0 result, i = _distinguishedName(dn, 0) if result is None: raise tldap.exceptions.InvalidDN("Cannot parse dn") if i != len(dn): raise tldap.exceptions.InvalidDN("Cannot parse dn past %s" % dn[i:]) return result
[docs] def dn2str(dn): """ This function takes a decomposed DN as parameter and returns a single string. It's the inverse to str2dn() but will always return a DN in LDAPv3 format compliant to RFC 4514. """ for rdn in dn: for atype, avalue, dummy in rdn: assert isinstance(atype, six.string_types) assert isinstance(avalue, six.string_types) assert dummy == 1 return ','.join([ '+'.join([ '='.join((atype, escape_dn_chars(avalue or ''))) for atype, avalue, dummy in rdn]) for rdn in dn ])
[docs] def explode_dn(dn, notypes=0, flags=0): """ explode_dn(dn [, notypes=0]) -> list This function takes a DN and breaks it up into its component parts. The notypes parameter is used to specify that only the component's attribute values be returned and not the attribute types. """ if not dn: return [] dn_decomp = str2dn(dn, flags) rdn_list = [] for rdn in dn_decomp: if notypes: rdn_list.append('+'.join([ escape_dn_chars(avalue or '') for atype, avalue, dummy in rdn ])) else: rdn_list.append('+'.join([ '='.join((atype, escape_dn_chars(avalue or ''))) for atype, avalue, dummy in rdn ])) return rdn_list
[docs] def explode_rdn(rdn, notypes=0, flags=0): """ explode_rdn(rdn [, notypes=0]) -> list This function takes a RDN and breaks it up into its component parts if it is a multi-valued RDN. The notypes parameter is used to specify that only the component's attribute values be returned and not the attribute types. """ if not rdn: return [] rdn_decomp = str2dn(rdn, flags)[0] if notypes: return [avalue or '' for atype, avalue, dummy in rdn_decomp] else: return ['='.join((atype, escape_dn_chars(avalue or ''))) for atype, avalue, dummy in rdn_decomp]