diff --git a/examples/passwd.py b/examples/passwd.py index e0fa413e6552c3a504c1aab5e853b69f0b7864e6..7a516eb8ae05c5ce2e549154009990e6a54fb592 100644 --- a/examples/passwd.py +++ b/examples/passwd.py @@ -1,11 +1,14 @@ +import logging import socketserver import pwd import grp import ldapserver +logging.basicConfig(level=logging.INFO) + class RequestHandler(ldapserver.LDAPRequestHandler): - subschema = ldapserver.schema.RFC2307BIS_SUBSCHEMA + subschema = ldapserver.SubschemaSubentry(ldapserver.schema.RFC2307BIS_SCHEMA, 'cn=Subschema') def do_search(self, basedn, scope, filterobj): yield from super().do_search(basedn, scope, filterobj) @@ -17,8 +20,8 @@ class RequestHandler(ldapserver.LDAPRequestHandler): for user in pwd.getpwall(): user_gids[user.pw_gid] = user_gids.get(user.pw_gid, set()) | {user.pw_name} yield self.subschema.Object(ldapserver.dn.DN('ou=users,dc=example,dc=com', uid=user.pw_name), **{ - 'objectClass': ['top', 'inetorgperson', 'organizationalperson', 'person', 'posixaccount'], - 'structuralObjectClass': ['organization'], + 'objectClass': ['top', 'organizationalperson', 'person', 'posixaccount'], + 'structuralObjectClass': ['organizationalperson'], 'uid': [user.pw_name], 'uidNumber': [user.pw_uid], 'gidNumber': [user.pw_gid], @@ -27,8 +30,8 @@ class RequestHandler(ldapserver.LDAPRequestHandler): for group in grp.getgrall(): members = set(group.gr_mem) | user_gids.get(group.gr_gid, set()) yield self.subschema.Object(ldapserver.dn.DN('ou=groups,dc=example,dc=com', cn=group.gr_name), **{ - 'objectClass': ['structuralobjectclass', 'objectclass', 'cn', 'description', 'gidnumber', 'uniquemember'], - 'structuralObjectClass': ['organization'], + 'objectClass': ['top', 'groupOfUniqueNames', 'posixGroup'], + 'structuralObjectClass': ['groupOfUniqueNames'], 'cn': [group.gr_name], 'gidNumber': [group.gr_gid], 'uniqueMember': [ldapserver.dn.DN('ou=user,dc=example,dc=com', uid=name) for name in members], diff --git a/examples/static.py b/examples/static.py deleted file mode 100644 index 3052141172459b5079da2345dff7a5ad17bff7c3..0000000000000000000000000000000000000000 --- a/examples/static.py +++ /dev/null @@ -1,16 +0,0 @@ -import socketserver -import ldapserver - -class RequestHandler(ldapserver.LDAPRequestHandler): - subschema = ldapserver.schema.RFC2307BIS_SUBSCHEMA - - static_objects = [ - subschema.Object('dc=example,dc=com', - objectClass=['top', 'dcObject', 'organization'], - structuralObjectClass=['organization'], - cn=['foo', 'bar'], - c=['test']) - ] - -if __name__ == '__main__': - socketserver.ThreadingTCPServer(('127.0.0.1', 3890), RequestHandler).serve_forever() diff --git a/ldapserver/dn.py b/ldapserver/dn.py index bcb77370139575e2bac62f730a63fe30bca0e24c..8e4ded8abbe5c0255db3a9f477dd1104b8a5cfcb 100644 --- a/ldapserver/dn.py +++ b/ldapserver/dn.py @@ -17,6 +17,7 @@ Limitations: import typing import unicodedata +import re from string import hexdigits as HEXDIGITS __all__ = ['DN', 'RDN', 'RDNAssertion'] @@ -62,9 +63,6 @@ class DN(tuple): def __str__(self): return ','.join(map(str, self)) - def __bytes__(self): - return str(self).encode('utf8') - def __repr__(self): return '<ldapserver.dn.DN %s>'%str(self) @@ -121,6 +119,35 @@ class DN(tuple): return None return self[0].value_normalized # pylint: disable=no-member +class DNWithUID(DN): + # pylint: disable=arguments-differ,no-member + def __new__(cls, dn, uid=None): + if not uid: + return dn + if not re.fullmatch(r"'[01]*'B", uid): + raise ValueError('Invalid uid value') + obj = super().__new__(cls, *dn) + obj.uid = uid + return obj + + @classmethod + def from_str(cls, expr): + dn_part, uid_part = (expr.rsplit('#', 1) + [''])[:2] + return cls(DN.from_str(dn_part), uid_part or None) + + def __str__(self): + return super().__str__() + '#' + self.uid + + def __repr__(self): + return '<ldapserver.dn.DNWithUID %s>'%str(self) + + def __eq__(self, obj): + return type(self) is type(obj) and super().__eq__(obj) and self.uid == obj.uid + + @property + def dn(self): + return DN(*self) + class RDN(tuple): '''Relative Distinguished Name consisting of one or more `RDNAssertion` objects''' def __new__(cls, *assertions, **kwargs): diff --git a/ldapserver/ldap.py b/ldapserver/ldap.py index 639d715abc76f9c74f81dfd7bee3176e570573cc..91d5ccdc5d9f582a9c0a58e1de8063aa30919a2d 100644 --- a/ldapserver/ldap.py +++ b/ldapserver/ldap.py @@ -358,7 +358,7 @@ class LDAPResultCode(enum.Enum): class LDAPResult(asn1.Sequence): BER_TAG = (5, True, 1) SEQUENCE_FIELDS = [ - (asn1.wrapenum(LDAPResultCode), 'resultCode', None, False), + (asn1.wrapenum(LDAPResultCode), 'resultCode', LDAPResultCode.success, False), (LDAPString, 'matchedDN', '', False), (LDAPString, 'diagnosticMessage', '', False), ] diff --git a/ldapserver/objects.py b/ldapserver/objects.py index 6140f96d0881458ee6c21d83ef62fac78579ce56..5b930e47400753c13d134f855cd86c0b2d45cc0c 100644 --- a/ldapserver/objects.py +++ b/ldapserver/objects.py @@ -1,56 +1,41 @@ +import collections.abc import enum from . import ldap, exceptions from .dn import DN -class FilterResult(enum.Enum): - TRUE = enum.auto() - FALSE = enum.auto() - UNDEFINED = enum.auto() - MAYBE_TRUE = enum.auto() # used by ObjectTemplate - -def match_to_filter_result(match_result): - if match_result is True: - return FilterResult.TRUE - if match_result is False: - return FilterResult.FALSE - return FilterResult.UNDEFINED - -def any_3value(iterable): - '''Extended three-valued logic equivalent of any builtin - - If all items are TRUE, return TRUE. Otherwise if any item is MAYBE_TRUE, - return MAYBE_TRUE. If neither TRUE nor MAYBE_TRUE are in items, but any - item is UNDEFINED, return UNDEFINED. Otherwise (all items are FALSE), - return FALSE.''' - result = FilterResult.FALSE - for item in iterable: - if item == FilterResult.TRUE: - return FilterResult.TRUE - elif item == FilterResult.MAYBE_TRUE: - result = FilterResult.MAYBE_TRUE - elif item == FilterResult.UNDEFINED and result == FilterResult.FALSE: - result = FilterResult.UNDEFINED - return result - -def all_3value(iterable): - '''Extended three-valued logic equivalent of all builtin - - If all items are TRUE, return TRUE. If any item is FALSE, return FALSE. - If no item is FALSE and any item is UNDEFINED, return UNDEFINED. - Otherwise (not item is FALSE or UNDEFINED and not all items are TRUE, - so at least one item is MAYBE_TRUE), return MAYBE_TRUE.''' - result = FilterResult.TRUE - for item in iterable: - if item == FilterResult.FALSE: - return FilterResult.FALSE - elif item == FilterResult.UNDEFINED: - result = FilterResult.UNDEFINED - elif item == FilterResult.MAYBE_TRUE and result == FilterResult.TRUE: - result = FilterResult.MAYBE_TRUE - return result - -class AttributeDict(dict): +class TypeKeysView(collections.abc.Set): + def __init__(self, attributes): + self.__attributes = attributes + + def __iter__(self): + for attribute_type, values in self.__attributes.items(): + if values: + yield attribute_type + + def __len__(self): + return len(list(iter(self))) + + def __contains__(self, value): + return bool(self.__attributes.get(value)) + +class TypeItemsView(collections.abc.Set): + def __init__(self, attributes): + self.__attributes = attributes + + def __iter__(self): + for attribute_type, values in self.__attributes.items(): + if values: + yield attribute_type, values + + def __len__(self): + return len(list(iter(self))) + + def __contains__(self, value): + key, values = value + return self.__attributes.get(key) == values + +class AttributeDict(collections.abc.MutableMapping): '''Special dictionary holding LDAP attribute values Attribute values can be set and accessed by their attribute type's numeric @@ -59,158 +44,75 @@ class AttributeDict(dict): attribute behaves the same as accessing an empty attribute. List items must conform to the attribute's syntax.''' def __init__(self, schema, **attributes): - super().__init__() + self.__attributes = {} self.schema = schema - for key, value in attributes.items(): - self[key] = value + for key, values in attributes.items(): + self[key] = values - def __contains__(self, key): - try: - return super().__contains__(self.schema.get_attribute_type(key).name) - except KeyError: - return False + def __getitem__(self, key): + return self.__attributes.setdefault(self.schema.attribute_types[key], []) def __setitem__(self, key, values): - super().__setitem__(self.schema.get_attribute_type(key).name, values) + self.__attributes[self.schema.attribute_types[key]] = values - def __getitem__(self, key): - canonical_oid = self.schema.get_attribute_type(key).name - if not super().__contains__(canonical_oid): - super().__setitem__(canonical_oid, []) - result = super().__getitem__(canonical_oid) - if callable(result): - return result() - return result + def __delitem__(self, key): + self[key] = [] - def setdefault(self, key, default=None): - canonical_oid = self.schema.get_attribute_type(key).name - return super().setdefault(canonical_oid, default) + def __iter__(self): + for attribute_type, values in self.__attributes.items(): + if values: + yield attribute_type.ref - def get(self, key, default=None): - return self[key] or default + def __len__(self): + return len(list(iter(self))) - def get_with_subtypes(self, key): + def get(self, key, default=None, subtypes=False): # pylint: disable=arguments-differ + attribute_type = self.schema.attribute_types.get(key) + attribute_types = [attribute_type] + if subtypes and attribute_type: + attribute_types += attribute_type.subtypes result = [] - for attribute_type in self.schema.get_attribute_type_and_subtypes(key): - result += self[attribute_type.name] + for attribute_type in attribute_types: + result += self.__attributes.get(attribute_type, []) + if not result: + result = default if default is not None else [] return result - def match_present(self, key): - attribute_type = self.schema.get_attribute_type(key) - if attribute_type is None: - return FilterResult.UNDEFINED - if self[attribute_type.name] != []: - return FilterResult.TRUE - else: - return FilterResult.FALSE - - def match_equal(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.equality.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.equality.match_equal(self.schema, attrval, assertion_value)), self.get_with_subtypes(key))) - - def match_substr(self, key, inital_substring, any_substrings, final_substring): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None or attribute_type.substr is None: - return FilterResult.UNDEFINED - if inital_substring: - inital_substring = attribute_type.equality.syntax.decode(self.schema, inital_substring) - if inital_substring is None: - return FilterResult.UNDEFINED - any_substrings = [attribute_type.equality.syntax.decode(self.schema, substring) for substring in any_substrings] - if None in any_substrings: - return FilterResult.UNDEFINED - if final_substring: - final_substring = attribute_type.equality.syntax.decode(self.schema, final_substring) - if final_substring is None: - return FilterResult.UNDEFINED - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.substr.match_substr(self.schema, attrval, inital_substring, any_substrings, final_substring)), self.get_with_subtypes(key))) - - def match_approx(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.equality.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.equality.match_approx(self.schema, attrval, assertion_value)), self.get_with_subtypes(key))) + def keys(self, types=False): # pylint: disable=arguments-differ + if not types: + return super().keys() + return TypeKeysView(self.__attributes) - def match_greater_or_equal(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.ordering is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.ordering.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.ordering.match_greater_or_equal(self.schema, attrval, assertion_value)), self.get_with_subtypes(key))) + def items(self, types=False): # pylint: disable=arguments-differ + if not types: + return super().items() + return TypeItemsView(self.__attributes) - def match_less(self, key, assertion_value): + def __contains__(self, key): try: - attribute_type = self.schema.get_attribute_type(key) + return bool(self[key]) except KeyError: - return FilterResult.UNDEFINED - if attribute_type.ordering is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.ordering.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.ordering.match_less(self.schema, attrval, assertion_value)), self.get_with_subtypes(key))) + return False - def match_less_or_equal(self, key, assertion_value): - return any_3value((self.match_equal(key, assertion_value), - self.match_less(key, assertion_value))) + def setdefault(self, key, default=False): + if key in self: + return self[key] + if default is None: + default = [] + self[key] = default + return default - def match_filter(self, filter_obj): - if isinstance(filter_obj, ldap.FilterAnd): - return all_3value(map(self.match_filter, filter_obj.filters)) - elif isinstance(filter_obj, ldap.FilterOr): - return any_3value(map(self.match_filter, filter_obj.filters)) - elif isinstance(filter_obj, ldap.FilterNot): - subresult = self.match_filter(filter_obj.filter) - if subresult == FilterResult.TRUE: - return FilterResult.FALSE - elif subresult == FilterResult.FALSE: - return FilterResult.TRUE - else: - return subresult - elif isinstance(filter_obj, ldap.FilterPresent): - return self.match_present(filter_obj.attribute) - elif isinstance(filter_obj, ldap.FilterEqual): - return self.match_equal(filter_obj.attribute, filter_obj.value) - elif isinstance(filter_obj, ldap.FilterSubstrings): - return self.match_substr(filter_obj.attribute, filter_obj.initial_substring, - filter_obj.any_substrings, filter_obj.final_substring) - elif isinstance(filter_obj, ldap.FilterApproxMatch): - return self.match_approx(filter_obj.attribute, filter_obj.value) - elif isinstance(filter_obj, ldap.FilterGreaterOrEqual): - return self.match_greater_or_equal(filter_obj.attribute, filter_obj.value) - elif isinstance(filter_obj, ldap.FilterLessOrEqual): - return self.match_less_or_equal(filter_obj.attribute, filter_obj.value) - else: - return FilterResult.UNDEFINED +class FilterResult(enum.Enum): + TRUE = enum.auto() + FALSE = enum.auto() + UNDEFINED = enum.auto() class Object(AttributeDict): def __init__(self, schema, dn, **attributes): super().__init__(schema, **attributes) self.dn = DN(dn) - def match_dn(self, basedn, scope): + def __search_match_dn(self, basedn, scope): if scope == ldap.SearchScope.baseObject: return self.dn == basedn elif scope == ldap.SearchScope.singleLevel: @@ -220,51 +122,139 @@ class Object(AttributeDict): else: return False + def __search_match_filter(self, filter_obj): + # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements,too-many-nested-blocks + if isinstance(filter_obj, ldap.FilterAnd): + result = FilterResult.TRUE + for subfilter in filter_obj.filters: + subresult = self.__search_match_filter(subfilter) + if subresult == FilterResult.FALSE: + return FilterResult.FALSE + elif subresult == FilterResult.UNDEFINED: + result = FilterResult.UNDEFINED + return result + elif isinstance(filter_obj, ldap.FilterOr): + result = FilterResult.FALSE + for subfilter in filter_obj.filters: + subresult = self.__search_match_filter(subfilter) + if subresult == FilterResult.TRUE: + return FilterResult.TRUE + elif subresult == FilterResult.UNDEFINED: + result = FilterResult.UNDEFINED + return result + elif isinstance(filter_obj, ldap.FilterNot): + subresult = self.__search_match_filter(filter_obj.filter) + if subresult == FilterResult.TRUE: + return FilterResult.FALSE + elif subresult == FilterResult.FALSE: + return FilterResult.TRUE + else: + return subresult + elif isinstance(filter_obj, (ldap.FilterPresent, + ldap.FilterEqual, + ldap.FilterSubstrings, + ldap.FilterApproxMatch, + ldap.FilterGreaterOrEqual, + ldap.FilterLessOrEqual)): + try: + attribute_type = self.schema.attribute_types[filter_obj.attribute] + except KeyError: + return FilterResult.UNDEFINED + values = self.get(filter_obj.attribute, subtypes=True) + try: + if isinstance(filter_obj, ldap.FilterPresent): + result = values != [] + elif isinstance(filter_obj, ldap.FilterEqual): + result = attribute_type.match_equal(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterSubstrings): + result = attribute_type.match_substr(values, + filter_obj.initial_substring, filter_obj.any_substrings, filter_obj.final_substring) + elif isinstance(filter_obj, ldap.FilterApproxMatch): + result = attribute_type.match_approx(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterGreaterOrEqual): + result = attribute_type.match_greater_or_equal(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterLessOrEqual): + result = attribute_type.match_less_or_equal(values, filter_obj.value) + else: + return FilterResult.UNDEFINED + return FilterResult.TRUE if result else FilterResult.FALSE + except exceptions.LDAPError: + return FilterResult.UNDEFINED + elif isinstance(filter_obj, ldap.FilterExtensibleMatch): + attribute_types = [] + matching_rule = None + try: + if filter_obj.type is not None and filter_obj.matchingRule is not None: + attribute_types = [self.schema.attribute_types[filter_obj.type]] + matching_rule = self.schema.matching_rules[filter_obj.matchingRule] + elif filter_obj.type is not None: + attribute_types = [self.schema.attribute_types[filter_obj.type]] + elif filter_obj.matchingRule is not None: + matching_rule = self.schema.matching_rules[filter_obj.matchingRule] + attribute_types = matching_rule.compatible_attribute_types + except KeyError: + pass + result = FilterResult.FALSE + for attribute_type in attribute_types: + values = self.get(attribute_type.oid, subtypes=True) + if filter_obj.dnAttributes: + for rdn in self.dn: + for assertion in rdn: + if assertion.attribute.lower() == attribute_type.ref.lower(): + values.append(assertion.value) + try: + if attribute_type.match_extensible(values, filter_obj.matchValue, matching_rule): + return FilterResult.TRUE + except exceptions.LDAPError: + result = FilterResult.UNDEFINED + return result + else: + return FilterResult.UNDEFINED + def match_search(self, base_obj, scope, filter_obj): - return self.match_dn(DN.from_str(base_obj), scope) and self.match_filter(filter_obj) == FilterResult.TRUE + return self.__search_match_dn(DN.from_str(base_obj), scope) and \ + self.__search_match_filter(filter_obj) == FilterResult.TRUE - def match_compare(self, attribute, value): - try: - attribute_type = self.schema.get_attribute_type(attribute) - except KeyError as exc: - raise exceptions.LDAPUndefinedAttributeType() from exc - if attribute_type.equality is None: - raise exceptions.LDAPInappropriateMatching() - value = attribute_type.equality.syntax.decode(self.schema, value) - if value is None: - raise exceptions.LDAPInvalidAttributeSyntax() - for attrval in self.get_with_subtypes(attribute): - if attribute_type.equality.match_equal(self.schema, attrval, value): - return True - return False - - def get_search_result_entry(self, attributes=None, types_only=False): + def search(self, base_obj, scope, filter_obj, attributes, types_only): + if not self.match_search(base_obj, scope, filter_obj): + return None selected_attributes = set() for selector in attributes or ['*']: if selector == '*': - selected_attributes |= set(self.schema.user_attribute_types) + selected_attributes |= self.schema.user_attribute_types elif selector == '1.1': continue - else: - try: - selected_attributes.add(self.schema.get_attribute_type(selector)) - except KeyError: - pass + elif selector in self.schema.attribute_types: + selected_attributes.add(self.schema.attribute_types[selector]) partial_attributes = [] - for attribute_type in selected_attributes: - values = self[attribute_type.name] - if values != []: + for attribute_type, values in self.items(types=True): + if attribute_type in selected_attributes: if types_only: values = [] - partial_attributes.append(ldap.PartialAttribute(attribute_type.name, [attribute_type.syntax.encode(self.schema, value) for value in values])) + encoded_values = [attribute_type.encode(value) for value in values] + partial_attributes.append(ldap.PartialAttribute(attribute_type.ref, encoded_values)) return ldap.SearchResultEntry(str(self.dn), partial_attributes) + def compare(self, dn, attribute, value): + try: + dn = DN.from_str(dn) + except ValueError as exc: + raise exceptions.LDAPNoSuchObject() from exc + if dn != self.dn: + raise exceptions.LDAPNoSuchObject() + try: + attribute_type = self.schema.attribute_types[attribute] + except KeyError as exc: + raise exceptions.LDAPUndefinedAttributeType() from exc + return attribute_type.match_equal(self.get(attribute_type, subtypes=True), value) + class RootDSE(Object): def __init__(self, schema, *args, **kwargs): super().__init__(schema, DN(), *args, **kwargs) + self.setdefault('objectClass', ['top']) def match_search(self, base_obj, scope, filter_obj): - return not base_obj and scope == ldap.SearchScope.baseObject and \ + return not DN.from_str(base_obj) and scope == ldap.SearchScope.baseObject and \ isinstance(filter_obj, ldap.FilterPresent) and \ filter_obj.attribute.lower() == 'objectclass' @@ -273,111 +263,21 @@ class WildcardValue: WILDCARD_VALUE = WildcardValue() +class TemplateFilterResult(enum.Enum): + TRUE = enum.auto() + FALSE = enum.auto() + UNDEFINED = enum.auto() + MAYBE_TRUE = enum.auto() + class ObjectTemplate(AttributeDict): def __init__(self, schema, parent_dn, rdn_attribute, **attributes): super().__init__(schema, **attributes) - self.parent_dn = parent_dn + self.parent_dn = DN(parent_dn) self.rdn_attribute = rdn_attribute - def match_present(self, key): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - values = self[attribute_type.name] - if values == []: - return FilterResult.FALSE - elif WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - else: - return FilterResult.TRUE - - def match_equal(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.equality.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - values = self.get_with_subtypes(key) - if WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.equality.match_equal(self.schema, attrval, assertion_value)), values)) - - def match_substr(self, key, inital_substring, any_substrings, final_substring): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None or attribute_type.substr is None: - return FilterResult.UNDEFINED - if inital_substring: - inital_substring = attribute_type.equality.syntax.decode(self.schema, inital_substring) - if inital_substring is None: - return FilterResult.UNDEFINED - any_substrings = [attribute_type.equality.syntax.decode(self.schema, substring) for substring in any_substrings] - if None in any_substrings: - return FilterResult.UNDEFINED - if final_substring: - final_substring = attribute_type.equality.syntax.decode(self.schema, final_substring) - if final_substring is None: - return FilterResult.UNDEFINED - values = self.get_with_subtypes(key) - if WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.substr.match_substr(self.schema, attrval, inital_substring, any_substrings, final_substring)), values)) - - def match_approx(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.equality is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.equality.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - values = self.get_with_subtypes(key) - if WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.equality.match_approx(self.schema, attrval, assertion_value)), values)) - - def match_greater_or_equal(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.ordering is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.ordering.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - values = self.get_with_subtypes(key) - if WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.ordering.match_greater_or_equal(self.schema, attrval, assertion_value)), values)) - - def match_less(self, key, assertion_value): - try: - attribute_type = self.schema.get_attribute_type(key) - except KeyError: - return FilterResult.UNDEFINED - if attribute_type.ordering is None: - return FilterResult.UNDEFINED - assertion_value = attribute_type.ordering.syntax.decode(self.schema, assertion_value) - if assertion_value is None: - return FilterResult.UNDEFINED - values = self.get_with_subtypes(key) - if WILDCARD_VALUE in values: - return FilterResult.MAYBE_TRUE - return any_3value(map(lambda attrval: match_to_filter_result(attribute_type.ordering.match_less(self.schema, attrval, assertion_value)), values)) - - def __extract_dn_constraints(self, basedn, scope): + def __match_extract_dn_constraints(self, basedn, scope): if scope == ldap.SearchScope.baseObject: - if basedn[1:] != self.parent_dn or basedn.object_attribute != self.rdn_attribute: + if basedn[1:] != self.parent_dn or basedn.object_attribute.lower() != self.rdn_attribute.lower(): return False, AttributeDict(self.schema) return True, AttributeDict(self.schema, **{self.rdn_attribute: [basedn.object_value]}) elif scope == ldap.SearchScope.singleLevel: @@ -385,46 +285,157 @@ class ObjectTemplate(AttributeDict): elif scope == ldap.SearchScope.wholeSubtree: if self.parent_dn.in_subtree_of(basedn): return True, AttributeDict(self.schema) - if basedn[1:] != self.parent_dn or basedn.object_attribute != self.rdn_attribute: + if basedn[1:] != self.parent_dn or basedn.object_attribute.lower() != self.rdn_attribute.lower(): return False, AttributeDict(self.schema) return True, AttributeDict(self.schema, **{self.rdn_attribute: [basedn.object_value]}) else: return False, AttributeDict(self.schema) - def match_dn(self, basedn, scope): + def __search_match_dn(self, basedn, scope): '''Return whether objects from this template might match the provided parameters''' - return self.__extract_dn_constraints(basedn, scope)[0] + return self.__match_extract_dn_constraints(basedn, scope)[0] - def extract_dn_constraints(self, basedn, scope): - return self.__extract_dn_constraints(basedn, scope)[1] + def __extract_dn_constraints(self, basedn, scope): + return self.__match_extract_dn_constraints(basedn, scope)[1] + + def __search_match_filter(self, filter_obj): + # pylint: disable=too-many-return-statements,too-many-branches,too-many-nested-blocks,too-many-statements + if isinstance(filter_obj, ldap.FilterAnd): + result = TemplateFilterResult.TRUE + for subfilter in filter_obj.filters: + subresult = self.__search_match_filter(subfilter) + if subresult == TemplateFilterResult.FALSE: + return TemplateFilterResult.FALSE + elif subresult == TemplateFilterResult.UNDEFINED: + result = TemplateFilterResult.UNDEFINED + elif subresult == TemplateFilterResult.MAYBE_TRUE and result == TemplateFilterResult.TRUE: + result = TemplateFilterResult.MAYBE_TRUE + return result + elif isinstance(filter_obj, ldap.FilterOr): + result = TemplateFilterResult.FALSE + for subfilter in filter_obj.filters: + subresult = self.__search_match_filter(subfilter) + if subresult == TemplateFilterResult.TRUE: + return TemplateFilterResult.TRUE + elif subresult == TemplateFilterResult.MAYBE_TRUE: + result = TemplateFilterResult.MAYBE_TRUE + elif subresult == TemplateFilterResult.UNDEFINED and result == TemplateFilterResult.FALSE: + result = TemplateFilterResult.UNDEFINED + return result + elif isinstance(filter_obj, ldap.FilterNot): + subresult = self.__search_match_filter(filter_obj.filter) + if subresult == TemplateFilterResult.TRUE: + return TemplateFilterResult.FALSE + elif subresult == TemplateFilterResult.FALSE: + return TemplateFilterResult.TRUE + else: + return subresult + elif isinstance(filter_obj, (ldap.FilterPresent, + ldap.FilterEqual, + ldap.FilterSubstrings, + ldap.FilterApproxMatch, + ldap.FilterGreaterOrEqual, + ldap.FilterLessOrEqual)): + try: + attribute_type = self.schema.attribute_types[filter_obj.attribute] + except KeyError: + return TemplateFilterResult.UNDEFINED + values = self.get(filter_obj.attribute, subtypes=True) + is_wildcard = WILDCARD_VALUE in values + if is_wildcard: + values = [] + try: + if isinstance(filter_obj, ldap.FilterPresent): + result = values != [] + elif isinstance(filter_obj, ldap.FilterEqual): + result = attribute_type.match_equal(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterSubstrings): + result = attribute_type.match_substr(values, + filter_obj.initial_substring, filter_obj.any_substrings, filter_obj.final_substring) + elif isinstance(filter_obj, ldap.FilterApproxMatch): + result = attribute_type.match_approx(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterGreaterOrEqual): + result = attribute_type.match_greater_or_equal(values, filter_obj.value) + elif isinstance(filter_obj, ldap.FilterLessOrEqual): + result = attribute_type.match_less_or_equal(values, filter_obj.value) + else: + return TemplateFilterResult.UNDEFINED + if result: + return TemplateFilterResult.TRUE + except exceptions.LDAPError: + return TemplateFilterResult.UNDEFINED + if is_wildcard: + return TemplateFilterResult.MAYBE_TRUE + return TemplateFilterResult.FALSE + elif isinstance(filter_obj, ldap.FilterExtensibleMatch): + attribute_types = [] + matching_rule = None + try: + if filter_obj.type is not None and filter_obj.matchingRule is not None: + attribute_types = [self.schema.attribute_types[filter_obj.type]] + matching_rule = self.schema.matching_rules[filter_obj.matchingRule] + elif filter_obj.type is not None: + attribute_types = [self.schema.attribute_types[filter_obj.type]] + elif filter_obj.matchingRule is not None: + matching_rule = self.schema.matching_rules[filter_obj.matchingRule] + attribute_types = matching_rule.compatible_attribute_types + except KeyError: + pass + result = TemplateFilterResult.FALSE + for attribute_type in attribute_types: + values = self.get(attribute_type.oid, subtypes=True) + if filter_obj.dnAttributes: + for rdn in self.parent_dn: + for assertion in rdn: + if assertion.attribute.lower() == attribute_type.ref.lower(): + values.append(assertion.value) + is_wildcard = WILDCARD_VALUE in values + if is_wildcard: + values = [] + is_undefined = False + try: + if attribute_type.match_extensible(values, filter_obj.matchValue, matching_rule): + return TemplateFilterResult.TRUE + except exceptions.LDAPError: + is_undefined = True + if is_undefined and result == TemplateFilterResult.FALSE: + result = TemplateFilterResult.UNDEFINED + elif not is_undefined and is_wildcard: + result = TemplateFilterResult.MAYBE_TRUE + return result + else: + return TemplateFilterResult.UNDEFINED - def extract_filter_constraints(self, filter_obj): + def __extract_filter_constraints(self, filter_obj): if isinstance(filter_obj, ldap.FilterEqual): try: - attribute_type = self.schema.get_attribute_type(filter_obj.attribute) + attribute_type = self.schema.attribute_types[filter_obj.attribute] except KeyError: return AttributeDict(self.schema) if attribute_type.equality is None: return AttributeDict(self.schema) - assertion_value = attribute_type.equality.syntax.decode(self.schema, filter_obj.value) + assertion_value = attribute_type.equality.syntax.decode(filter_obj.value) if assertion_value is None: return AttributeDict(self.schema) return AttributeDict(self.schema, **{filter_obj.attribute: [assertion_value]}) if isinstance(filter_obj, ldap.FilterAnd): result = AttributeDict(self.schema) for subfilter in filter_obj.filters: - for name, values in self.extract_filter_constraints(subfilter).items(): + for name, values in self.__extract_filter_constraints(subfilter).items(): result[name] += values return result return AttributeDict(self.schema) def match_search(self, base_obj, scope, filter_obj): '''Return whether objects based on this template might match the search parameters''' - return self.match_dn(DN.from_str(base_obj), scope) and self.match_filter(filter_obj) in (FilterResult.TRUE, FilterResult.MAYBE_TRUE) + print(base_obj, scope, filter_obj, self.__search_match_filter(filter_obj)) + return self.__search_match_dn(DN.from_str(base_obj), scope) and \ + self.__search_match_filter(filter_obj) in (TemplateFilterResult.TRUE, + TemplateFilterResult.MAYBE_TRUE) def extract_search_constraints(self, base_obj, scope, filter_obj): - constraints = self.extract_filter_constraints(filter_obj) - for key, values in self.extract_dn_constraints(DN.from_str(base_obj), scope).items(): + constraints = self.__extract_filter_constraints(filter_obj) + for key, values in self.__extract_dn_constraints(DN.from_str(base_obj), scope).items(): constraints[key] += values return constraints @@ -446,10 +457,11 @@ class SubschemaSubentry(Object): self['subschemaSubentry'] = [self.dn] self['structuralObjectClass'] = ['subtree'] self['objectClass'] = ['top', 'subtree', 'subschema'] - self['objectClasses'] = schema.object_classes - self['ldapSyntaxes'] = schema.syntaxes - self['matchingRules'] = schema.matching_rules - self['attributeTypes'] = schema.attribute_types + self['objectClasses'] = schema.object_class_definitions + self['ldapSyntaxes'] = schema.syntax_definitions + self['matchingRules'] = schema.matching_rule_definitions + self['attributeTypes'] = schema.attribute_type_definitions + self['matchingRuleUse'] = schema.matching_rule_use_definitions # pylint: disable=invalid-name self.AttributeDict = lambda **attributes: AttributeDict(schema, **attributes) self.Object = lambda *args, **attributes: Object(schema, *args, subschemaSubentry=[self.dn], **attributes) diff --git a/ldapserver/rfc4518_stringprep.py b/ldapserver/rfc4518_stringprep.py index d4d07b02d9def7ff732ea8e97443cac3eb2dc1d8..6e4c93c2a06fff0346de78518c0d06fd5c3658ea 100644 --- a/ldapserver/rfc4518_stringprep.py +++ b/ldapserver/rfc4518_stringprep.py @@ -208,9 +208,9 @@ def prepare_map(value, matching_type=MatchingType.EXACT_STRING): # The output is the mapped string. new_value = '' for char in value: - if char in MAPPED_TO_NOTHING: + if ord(char) in MAPPED_TO_NOTHING: continue - if char in MAPPED_TO_SPACE: + if ord(char) in MAPPED_TO_SPACE: char = ' ' # No idea what "stored prefix string matching" is supposed to be if matching_type in (MatchingType.CASE_IGNORE_STRING, diff --git a/ldapserver/schema/__init__.py b/ldapserver/schema/__init__.py index 0e4298dd7fa8beddcbdee95426b25d1408d14798..8dda82c744ee00e9579c2cc68bda7406bf141b40 100644 --- a/ldapserver/schema/__init__.py +++ b/ldapserver/schema/__init__.py @@ -1,14 +1,256 @@ -from .types import * -from . import rfc4517, rfc4512, rfc4519, rfc4524, rfc3112, rfc2307bis, rfc2079, rfc2252, rfc2798, rfc4523, rfc1274 +from .types import Schema +from .definitions import * +from . import syntaxes, matching_rules -# Core LDAP Schema -RFC4519_SUBSCHEMA = Schema( rfc4519.object_classes.ALL, rfc4519.attribute_types.ALL, rfc4519.matching_rules.ALL, rfc4519.syntaxes.ALL) +RFC4512_ATTRIBUTE_TYPES = [ + "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + "( 2.5.18.3 NAME 'creatorsName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.18.1 NAME 'createTimestamp' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.18.4 NAME 'modifiersName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.18.2 NAME 'modifyTimestamp' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.21.9 NAME 'structuralObjectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) ", + "( 2.5.21.10 NAME 'governingStructureRule' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.18.10 NAME 'subschemaSubentry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + "( 2.5.21.6 NAME 'objectClasses' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation )", + "( 2.5.21.5 NAME 'attributeTypes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation )", + "( 2.5.21.4 NAME 'matchingRules' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation )", + "( 2.5.21.8 NAME 'matchingRuleUse' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation )", + "( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation )", + "( 2.5.21.2 NAME 'dITContentRules' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 USAGE directoryOperation )", + "( 2.5.21.1 NAME 'dITStructureRules' EQUALITY integerFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 USAGE directoryOperation )", + "( 2.5.21.7 NAME 'nameForms' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 USAGE directoryOperation )", + "( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE dSAOperation )", + "( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )", + "( 1.3.6.1.4.1.1466.101.120.13 NAME 'supportedControl' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )", + "( 1.3.6.1.4.1.1466.101.120.7 NAME 'supportedExtension' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )", + "( 1.3.6.1.4.1.4203.1.3.5 NAME 'supportedFeatures' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )", + "( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )", + "( 1.3.6.1.4.1.1466.101.120.14 NAME 'supportedSASLMechanisms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE dSAOperation )", +] +RFC4512_OBJECT_CLASSES = [ + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName )", + "( 1.3.6.1.4.1.1466.101.120.111 NAME 'extensibleObject' SUP top AUXILIARY )", + "( 2.5.20.1 NAME 'subschema' AUXILIARY MAY ( dITStructureRules $ nameForms $ ditContentRules $ objectClasses $ attributeTypes $ matchingRules $ matchingRuleUse ) )", +] +CORE_SCHEMA = RFC4512_SCHEMA = Schema(syntax_definitions=syntaxes.ALL, matching_rule_definitions=matching_rules.ALL, attribute_type_definitions=RFC4512_ATTRIBUTE_TYPES, object_class_definitions=RFC4512_OBJECT_CLASSES) -# COSINE LDAP/X.500 Schema -RFC4524_SUBSCHEMA = Schema(rfc4524.object_classes.ALL, rfc4524.attribute_types.ALL, rfc4524.matching_rules.ALL, rfc4524.syntaxes.ALL) +RFC4519_ATTRIBUTE_TYPES = [ + "( 2.5.4.15 NAME 'businessCategory' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.6 NAME ( 'c' 'countryName' ) SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.11 SINGLE-VALUE )", + "( 2.5.4.3 NAME ( 'cn' 'commonName' ) SUP name )", + "( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domainComponent' ) EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )", + "( 2.5.4.13 NAME 'description' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.27 NAME 'destinationIndicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )", + "( 2.5.4.49 NAME 'distinguishedName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + "( 2.5.4.46 NAME 'dnQualifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )", + "( 2.5.4.47 NAME 'enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )", + "( 2.5.4.23 NAME 'facsimileTelephoneNumber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 )", + "( 2.5.4.44 NAME 'generationQualifier' SUP name )", + "( 2.5.4.42 NAME ( 'givenName' 'gn' ) SUP name )", + "( 2.5.4.51 NAME 'houseIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.43 NAME 'initials' SUP name )", + "( 2.5.4.25 NAME 'internationalISDNNumber' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )", + "( 2.5.4.7 NAME ( 'l' 'localityName' ) SUP name )", + "( 2.5.4.31 NAME 'member' SUP distinguishedName )", + "( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.10 NAME ( 'o' 'organizationName' ) SUP name )", + "( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) SUP name )", + "( 2.5.4.32 NAME 'owner' SUP distinguishedName )", + "( 2.5.4.19 NAME 'physicalDeliveryOfficeName' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.16 NAME 'postalAddress' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )", + "( 2.5.4.17 NAME 'postalCode' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.18 NAME 'postOfficeBox' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.28 NAME 'preferredDeliveryMethod' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-VALUE )", + "( 2.5.4.26 NAME 'registeredAddress' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )", + "( 2.5.4.33 NAME 'roleOccupant' SUP distinguishedName )", + "( 2.5.4.14 NAME 'searchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 )", + "( 2.5.4.34 NAME 'seeAlso' SUP distinguishedName )", + "( 2.5.4.5 NAME 'serialNumber' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )", + "( 2.5.4.4 NAME ( 'sn' 'surname' ) SUP name )", + "( 2.5.4.8 NAME 'st' SUP name )", + "( 2.5.4.9 NAME 'street' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.20 NAME 'telephoneNumber' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )", + "( 2.5.4.22 NAME 'teletexTerminalIdentifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )", + "( 2.5.4.21 NAME 'telexNumber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )", + "( 2.5.4.12 NAME 'title' SUP name )", + "( 0.9.2342.19200300.100.1.1 NAME ( 'uid' 'userid' ) EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.50 NAME 'uniqueMember' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )", + "( 2.5.4.35 NAME 'userPassword' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", + "( 2.5.4.24 NAME 'x121Address' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )", + "( 2.5.4.45 NAME 'x500UniqueIdentifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )", +] +RFC4519_OBJECT_CLASSES = [ + "( 2.5.6.11 NAME 'applicationProcess' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ description ) )", + "( 2.5.6.2 NAME 'country' SUP top STRUCTURAL MUST c MAY ( searchGuide $ description ) )", + "( 1.3.6.1.4.1.1466.344 NAME 'dcObject' SUP top AUXILIARY MUST dc )", + "( 2.5.6.14 NAME 'device' SUP top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) )", + "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )", + "( 2.5.6.17 NAME 'groupOfUniqueNames' SUP top STRUCTURAL MUST ( uniqueMember $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )", + "( 2.5.6.3 NAME 'locality' SUP top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) )", + "( 2.5.6.4 NAME 'organization' SUP top STRUCTURAL MUST o MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )", + "( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )", + "( 2.5.6.8 NAME 'organizationalRole' SUP top STRUCTURAL MUST cn MAY ( x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ seeAlso $ roleOccupant $ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l $ description ) )", + "( 2.5.6.5 NAME 'organizationalUnit' SUP top STRUCTURAL MUST ou MAY ( businessCategory $ description $ destinationIndicator $ facsimileTelephoneNumber $ internationalISDNNumber $ l $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $ searchGuide $ seeAlso $ st $ street $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ userPassword $ x121Address ) )", + "( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )", + "( 2.5.6.10 NAME 'residentialPerson' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l ) )", + "( 1.3.6.1.1.3.1 NAME 'uidObject' SUP top AUXILIARY MUST uid )", +] +RFC4519_SCHEMA = CORE_SCHEMA.extend(attribute_type_definitions=RFC4519_ATTRIBUTE_TYPES, object_class_definitions=RFC4519_OBJECT_CLASSES) -# inetOrgPerson Schema -RFC2798_SUBSCHEMA = Schema(rfc2798.object_classes.ALL, rfc2798.attribute_types.ALL, rfc2798.matching_rules.ALL, rfc2798.syntaxes.ALL) +RFC4523_ATTRIBUTE_TYPES = [ + "( 2.5.4.36 NAME 'userCertificate' DESC 'X.509 user certificate' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )", + "( 2.5.4.37 NAME 'cACertificate' DESC 'X.509 CA certificate' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )", + "( 2.5.4.40 NAME 'crossCertificatePair' DESC 'X.509 cross certificate pair' EQUALITY certificatePairExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 )", + "( 2.5.4.39 NAME 'certificateRevocationList' DESC 'X.509 certificate revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )", + "( 2.5.4.38 NAME 'authorityRevocationList' DESC 'X.509 authority revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )", + "( 2.5.4.53 NAME 'deltaRevocationList' DESC 'X.509 delta revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )", + "( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'X.509 supported algorithms' EQUALITY algorithmIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )", +] +RFC4523_OBJECT_CLASSES = [ + "( 2.5.6.21 NAME 'pkiUser' DESC 'X.509 PKI User' SUP top AUXILIARY MAY userCertificate )", + "( 2.5.6.22 NAME 'pkiCA' DESC 'X.509 PKI Certificate Authority' SUP top AUXILIARY MAY ( cACertificate $ certificateRevocationList $ authorityRevocationList $ crossCertificatePair ) )", + "( 2.5.6.19 NAME 'cRLDistributionPoint' DESC 'X.509 CRL distribution point' SUP top STRUCTURAL MUST cn MAY ( certificateRevocationList $ authorityRevocationList $ deltaRevocationList ) )", + "( 2.5.6.23 NAME 'deltaCRL' DESC 'X.509 delta CRL' SUP top AUXILIARY MAY deltaRevocationList )", + "( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'X.521 strong authentication user' SUP top AUXILIARY MUST userCertificate )", + "( 2.5.6.18 NAME 'userSecurityInformation' DESC 'X.521 user security information' SUP top AUXILIARY MAY ( supportedAlgorithms ) )", + "( 2.5.6.16 NAME 'certificationAuthority' DESC 'X.509 certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList $ certificateRevocationList $ cACertificate ) MAY crossCertificatePair )", + "( 2.5.6.16.2 NAME 'certificationAuthority-V2' DESC 'X.509 certificate authority, version 2' SUP certificationAuthority AUXILIARY MAY deltaRevocationList )", +] +RFC4523_SCHEMA = RFC4519_SCHEMA.extend(attribute_type_definitions=RFC4523_ATTRIBUTE_TYPES, object_class_definitions=RFC4523_OBJECT_CLASSES) -# Extended RFC2307 (NIS) Schema -RFC2307BIS_SUBSCHEMA = Schema(rfc2307bis.object_classes.ALL, rfc2307bis.attribute_types.ALL, rfc2307bis.matching_rules.ALL, rfc2307bis.syntaxes.ALL) +RFC4524_ATTRIBUTE_TYPES = [ + "( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + "( 0.9.2342.19200300.100.1.38 NAME 'associatedName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + "( 0.9.2342.19200300.100.1.48 NAME 'buildingName' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.43 NAME 'co' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + "( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.5 NAME 'drink' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.20 NAME 'homePhone' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )", + "( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )", + "( 0.9.2342.19200300.100.1.9 NAME 'host' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.4 NAME 'info' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )", + "( 0.9.2342.19200300.100.1.3 NAME 'mail' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )", + "( 0.9.2342.19200300.100.1.10 NAME 'manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + "( 0.9.2342.19200300.100.1.41 NAME 'mobile' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )", + "( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.42 NAME 'pager' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )", + "( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.21 NAME 'secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + "( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + "( 0.9.2342.19200300.100.1.8 NAME 'userClass' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", +] +RFC4524_OBJECT_CLASSES = [ + "( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCTURAL MUST uid MAY ( description $ seeAlso $ l $ o $ ou $ host ) )", + "( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUCTURAL MUST documentIdentifier MAY ( cn $ description $ seeAlso $ l $ o $ ou $ documentTitle $ documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) )", + "( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top STRUCTURAL MUST cn MAY ( description $ l $ o $ ou $ seeAlso $ telephonenumber ) )", + "( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCTURAL MUST dc MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description $ o $ associatedName ) )", + "( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' SUP top AUXILIARY MUST associatedDomain )", + "( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP country STRUCTURAL MUST co )", + "( 0.9.2342.19200300.100.4.14 NAME 'rFC822localPart' SUP domain STRUCTURAL MAY ( cn $ description $ destinationIndicator $ facsimileTelephoneNumber $ internationaliSDNNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $ seeAlso $ sn $ street $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ x121Address ) )", + "( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURAL MUST cn MAY ( roomNumber $ description $ seeAlso $ telephoneNumber ) )", + "( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' SUP top AUXILIARY MUST userPassword )", +] +COSINE_SCHEMA = RFC4524_SCHEMA = RFC4519_SCHEMA.extend(attribute_type_definitions=RFC4524_ATTRIBUTE_TYPES, object_class_definitions=RFC4524_OBJECT_CLASSES) + +RFC3112_ATTRIBUTE_TYPES = [ + "( 1.3.6.1.4.1.4203.1.3.3 NAME 'supportedAuthPasswordSchemes' DESC 'supported password storage schemes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} USAGE dSAOperation )", + "( 1.3.6.1.4.1.4203.1.3.4 NAME 'authPassword' DESC 'password authentication information' EQUALITY 1.3.6.1.4.1.4203.1.2.2 SYNTAX 1.3.6.1.4.1.4203.1.1.2 )", +] +RFC3112_OBJECT_CLASSES = [ + "( 1.3.6.1.4.1.4203.1.4.7 NAME 'authPasswordObject' DESC 'authentication password mix in class' AUXILIARY MAY authPassword )", +] +RFC3112_SCHEMA = CORE_SCHEMA.extend(attribute_type_definitions=RFC3112_ATTRIBUTE_TYPES, object_class_definitions=RFC3112_OBJECT_CLASSES) + +RFC2079_ATTRIBUTE_TYPES = [ + "( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' DESC 'Uniform Resource Identifier with optional label' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", +] +RFC2079_OBJECT_CLASSES = [ + "( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'object that contains the URI attribute type' SUP top AUXILIARY MAY labeledURI )", +] +RFC2079_SCHEMA = CORE_SCHEMA.extend(attribute_type_definitions=RFC2079_ATTRIBUTE_TYPES, object_class_definitions=RFC2079_OBJECT_CLASSES) + +RFC2798_ATTRIBUTE_TYPES = [ + # Originally from RFC1274, but updated and used by RFC2798 + "( 0.9.2342.19200300.100.1.55 NAME 'audio' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{250000} )", + # Also from RFC1274 and updated by RFC2798, but lacks SYNTAX, so "Fax" is added here + "( 0.9.2342.19200300.100.1.7 NAME 'photo' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23 )", + + "( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC 'identifies a department within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'preferred name of a person to be used when displaying entries' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'numerically identifies an employee within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )", + "( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC 'preferred written or spoken language for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' DESC 'signed message used to support S/MIME' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )", + "( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'PKCS #12 PFX PDU for exchange of personal identity information' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )", +] +RFC2798_OBJECT_CLASSES = [ + "( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' SUP organizationalPerson STRUCTURAL MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )", +] +INETORG_SCHMEA = RFC2798_SCHEMA = (RFC4524_SCHEMA|RFC2079_SCHEMA|RFC4523_SCHEMA).extend(attribute_type_definitions=RFC2798_ATTRIBUTE_TYPES, object_class_definitions=RFC2798_OBJECT_CLASSES) + +RFC2307BIS_ATTRIBUTE_TYPES = [ + "( 1.3.6.1.1.1.1.0 NAME 'uidNumber' DESC 'An integer uniquely identifying a user in an administrative domain' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.1 NAME 'gidNumber' DESC 'An integer uniquely identifying a group in an administrative domain' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgroup triple' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' DESC 'Service port number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' DESC 'Service protocol name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' DESC 'IP protocol number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' DESC 'ONC RPC number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC2373' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + "( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP network omitting leading zeros, eg. 192.168' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netmask omitting leading zeros, eg. 255.255.255.0' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + "( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootparamd parameter' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + "( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image name' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + "( 1.3.6.1.1.1.1.26 NAME 'nisMapName' DESC 'Name of a generic NIS map' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} )", + "( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' DESC 'A generic NIS entry' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.28 NAME 'nisPublicKey' DESC 'NIS public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.29 NAME 'nisSecretKey' DESC 'NIS secret key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.30 NAME 'nisDomain' DESC 'NIS domain' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )", + "( 1.3.6.1.1.1.1.31 NAME 'automountMapName' DESC 'automount Map Name' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.32 NAME 'automountKey' DESC 'Automount Key value' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.33 NAME 'automountInformation' DESC 'Automount information' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )", +] +RFC2307BIS_OBJECT_CLASSES = [ + "( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( authPassword $ userPassword $ loginShell $ gecos $ description ) )", + "( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( authPassword $ userPassword $ description $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag ) )", + "( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST gidNumber MAY ( authPassword $ userPassword $ memberUid $ description ) )", + "( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names; the distinguished value of the cn attribute denotes the service\\27s canonical name' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipServiceProtocol ) MAY description )", + "( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of an IP protocol. Maps a protocol number to one or more names. The distinguished value of the cn attribute denotes the protocol canonical name' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber ) MAY description )", + "( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an Open Network Computing (ONC) [RFC1057] Remote Procedure Call (RPC) binding. This class maps an ONC RPC number to a name. The distinguished value of the cn attribute denotes the RPC service canonical name' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber ) MAY description )", + "( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the host\\27s canonical name. Device SHOULD be used as a structural class' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( authPassword $ userPassword $ l $ description $ manager ) )", + "( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a network. The distinguished value of the cn attribute denotes the network canonical name' SUP top STRUCTURAL MUST ipNetworkNumber MAY ( cn $ ipNetmaskNumber $ l $ description $ manager ) )", + "( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of a netgroup. May refer to other netgroups' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )", + "( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstraction of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )", + "( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) )", + "( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device with a MAC address; device SHOULD be used as a structural class' SUP top AUXILIARY MAY macAddress )", + "( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device with boot parameters; device SHOULD be used as a structural class' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) )", + "( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' DESC 'An object with a public and secret key' SUP top AUXILIARY MUST ( cn $ nisPublicKey $ nisSecretKey ) MAY ( uidNumber $ description ) )", + "( 1.3.6.1.1.1.2.15 NAME 'nisDomainObject' DESC 'Associates a NIS domain with a naming context' SUP top AUXILIARY MUST nisDomain )", + "( 1.3.6.1.1.1.2.16 NAME 'automountMap' SUP top STRUCTURAL MUST ( automountMapName ) MAY description )", + "( 1.3.6.1.1.1.2.17 NAME 'automount' DESC 'Automount information' SUP top STRUCTURAL MUST ( automountKey $ automountInformation ) MAY description )", + "( 1.3.6.1.1.1.2.18 NAME 'groupOfMembers' DESC 'A group with members (DNs)' SUP top STRUCTURAL MUST cn MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description $ member ) )", +] +RFC2307BIS_SCHEMA = (RFC4524_SCHEMA|RFC3112_SCHEMA).extend(attribute_type_definitions=RFC2307BIS_ATTRIBUTE_TYPES, object_class_definitions=RFC2307BIS_OBJECT_CLASSES) diff --git a/ldapserver/schema/definitions.py b/ldapserver/schema/definitions.py new file mode 100644 index 0000000000000000000000000000000000000000..c52c745c2bb7c913afd17a8931e588dc41dc5e20 --- /dev/null +++ b/ldapserver/schema/definitions.py @@ -0,0 +1,524 @@ +import enum +import re + +from .. import exceptions + +__all__ = [ + 'SyntaxDefinition', + 'MatchingRuleKind', + 'MatchingRuleDefinition', + 'MatchingRuleUseDefinition', + 'AttributeTypeUsage', + 'AttributeTypeDefinition', + 'ObjectClassKind', + 'ObjectClassDefinition', +] + +def escape(string): + return string.replace('\\', '\\5C').replace('\'', '\\27') + +def qdescr_to_token(descr): + return '\''+descr+'\'' + +def qdescrs_to_tokens(descrs): + if len(descrs) == 1: + return [qdescr_to_token(descrs[0])] + return ['('] + [qdescr_to_token(descr) for descr in descrs] + [')'] + +def oids_to_tokens(oids): + if len(oids) == 1: + return [oids[0]] + tokens = ['(', oids[0]] + for oid in oids[1:]: + tokens += ['$', oid] + return tokens + [')'] + +def qdstring_to_token(qdstring): + return '\''+escape(qdstring)+'\'' + +def qdstrings_to_tokens(qdstrings): + if len(qdstrings) == 1: + return [qdstring_to_token(qdstrings[0])] + return ['('] + [qdstring_to_token(qdstring) for qdstring in qdstrings] + [')'] + +def extensions_to_tokens(extensions): + tokens = [] + if not extensions: + return [] + for key, values in extensions.items(): + if not key.startswith('X-'): + raise ValueError('Extention names must start with "X-"') + tokens += [key] + qdstrings_to_tokens(values) + return tokens + +def tokenize(string): + tokens = [] + offset = 0 + while string: + match = re.match(r" *([()$]|[A-Za-z0-9.{}-]+|'[^']*') *", string) + if not match: + string_abbrev = string[:20] + '...' + raise ValueError(f'Unrecognized token at offset {offset}: "{string_abbrev}"') + tokens.append(match.groups()[0]) + string = string[match.end():] + offset += match.end() + return tokens + +def pop_token(tokens): + if not tokens: + raise ValueError('Unexpected end of input') + return tokens.pop(0) + +def check_token(tokens, expected_token): + if not tokens or tokens[0] != expected_token: + return False + tokens.pop(0) + return True + +def parse_token(tokens, expected_token): + token = pop_token(tokens) + if token != expected_token: + raise ValueError(f'Expected "{expected_token}" but got "{token}"') + return token + +def parse_numericoid(tokens): + token = pop_token(tokens) + if not re.fullmatch(r"([0-9]|[1-9][0-9]*)(\.([0-9]|[1-9][0-9]*))*", token): + raise ValueError(f'Invalid numeric OID "{token}"') + return token + +def parse_qdescr(tokens): + token = pop_token(tokens) + match = re.fullmatch(r"'([A-Za-z][A-Za-z0-9-]*)'", token) + if not match: + raise ValueError(f'Invalid quoted descriptor "{token}"') + return match.groups()[0] + +def parse_qdescrs(tokens): + if check_token(tokens, '('): + result = [] + while not check_token(tokens, ')'): + result.append(parse_qdescr(tokens)) + return result + return [parse_qdescr(tokens)] + +def parse_qdstring(tokens): + token = pop_token(tokens) + match = re.fullmatch(r"'(([^'\\]|\\27|\\5C|\\5c)+)'", token) + if not match: + raise ValueError(f'Invalid quoted string "{token}"') + return match.groups()[0].replace('\\27', '\'').replace('\\5C', '\\').replace('\\5c', '\\') + +def parse_qdstrings(tokens): + if check_token(tokens, '('): + result = [] + while not check_token(tokens, ')'): + result.append(parse_qdstring(tokens)) + return result + return [parse_qdstring(tokens)] + +def parse_oid(tokens): + token = pop_token(tokens) + if not re.fullmatch(r'([0-9]|[1-9][0-9]*)(\.([0-9]|[1-9][0-9]*))*|[A-Za-z][A-Za-z0-9-]*', token): + raise ValueError(f'Invalid OID "{token}"') + return token + +def parse_oids(tokens): + if check_token(tokens, '('): + result = [parse_oid(tokens)] + while not check_token(tokens, ')'): + parse_token(tokens, '$') + result.append(parse_oid(tokens)) + return result + return [parse_oid(tokens)] + +def parse_noidlen(tokens): + token = pop_token(tokens) + match = re.fullmatch(r"(([0-9]|[1-9][0-9]*)(\.([0-9]|[1-9][0-9]*))*)(|\{([0-9]|[1-9][0-9]*)\})", token) + if not match: + raise ValueError(f'Invalid numeric OID with optional length "{token}"') + noid, _, _, _, _, length = match.groups() + if length is not None: + length = int(length) + return noid, length + +def parse_extensions(tokens): + results = {} + while tokens and re.fullmatch(r"X-[A-Z_-]+", tokens[0]): + name = tokens.pop(0) + values = parse_qdstrings(tokens) + results[name] = values + return results + +class SyntaxDefinition: + def __init__(self, oid, desc='', extensions=None, extra_compatability_tags=None): + self.oid = oid + self.desc = desc + self.extensions = extensions or {} + self.compatability_tags = {oid} | set(extra_compatability_tags or tuple()) + + def __str__(self): + tokens = ['(', self.oid] + if self.desc: + tokens += ['DESC', qdstring_to_token(self.desc)] + tokens += extensions_to_tokens(self.extensions) + [')'] + return ' '.join(tokens) + + @property + def first_component_oid(self): + return self.oid + + def encode(self, schema, value): + '''Encode native value to its LDAP-specific encoding + + :param schema: Schema of the object in whose context encoding takes place + :type schema: Schema + :param value: native value (depends on syntax) + :type value: any + + :returns: LDAP-specific encoding of the value + :rtype: bytes''' + raise NotImplementedError() + + def decode(self, schema, raw_value): + '''Decode LDAP-specific encoding of a value to a native value + + :param schema: Schema of the object in whose context decoding takes place + :type schema: Schema + :param raw_value: LDAP-specific encoding of the value + :type raw_value: bytes + + :returns: native value (depends on syntax) + :rtype: any + :raises exceptions.LDAPError: if raw_value is invalid''' + raise exceptions.LDAPInvalidAttributeSyntax() + +class MatchingRuleKind(enum.Enum): + EQUALITY = enum.auto() + ORDERING = enum.auto() + SUBSTR = enum.auto() + +class MatchingRuleDefinition: + # pylint: disable=too-many-arguments + def __init__(self, oid, name=None, desc='', obsolete=False, syntax=None, extensions=None, compatability_tag=None, kind=None): + if not syntax: + raise ValueError('syntax must be specified') + if not kind: + raise ValueError('kind must be specified') + self.oid = oid + self.syntax = syntax + self.name = name or [] + self.desc = desc + self.obsolete = obsolete + self.extensions = extensions or {} + self.compatability_tag = compatability_tag or syntax + self.kind = kind + + def __str__(self): + tokens = ['(', self.oid] + if self.name: + tokens += ['NAME'] + qdescrs_to_tokens(self.name) + if self.desc: + tokens += ['DESC', qdstring_to_token(self.desc)] + if self.obsolete: + tokens += ['OBSOLETE'] + tokens += ['SYNTAX', self.syntax] + tokens += extensions_to_tokens(self.extensions) + [')'] + return ' '.join(tokens) + + @property + def first_component_oid(self): + return self.oid + + def match_equal(self, schema, attribute_values, assertion_value): + '''Return whether any attribute value equals assertion value + + Only available for EQUALITY matching rules. + + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + raise exceptions.LDAPInappropriateMatching() + + def match_approx(self, schema, attribute_values, assertion_value): + '''Return whether any attribute value approximatly equals assertion value + + Only available for EQUALITY matching rules. + + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + return self.match_equal(schema, attribute_values, assertion_value) + + def match_less(self, schema, attribute_values, assertion_value): + '''Return whether any attribute value is less than assertion value + + Only available for ORDERING matching rules. + + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + raise exceptions.LDAPInappropriateMatching() + + def match_greater_or_equal(self, schema, attribute_values, assertion_value): + '''Return whether any attribute value is greater than or equal to assertion value + + Only available for ORDERING matching rules. + + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + raise exceptions.LDAPInappropriateMatching() + + def match_substr(self, schema, attribute_values, inital_substring, any_substrings, final_substring): + '''Return whether any attribute value matches a substring assertion + + Only available for SUBSTR matching rules. + + The type of `inital_substring`, `any_substrings` and `final_substring` + depends on the syntax of the attribute's equality matching rule! + + :param schema: Schema of the object whose attribute values are matched + :type schema: Schema + :param attribute_values: Attribute values (type according to attribute's syntax) + :type attribute_values: List of any + :param inital_substring: Substring to match the beginning (optional) + :type inital_substring: any + :param any_substrings: List of substrings to match between initial and final in order + :type any_substrings: list of any + :param final_substring: Substring to match the end (optional) + :type final_substring: any + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + raise exceptions.LDAPInappropriateMatching() + +class MatchingRuleUseDefinition: + def __init__(self, oid, name=None, desc='', obsolete=False, applies=None, extensions=None): + self.oid = oid + self.name = name or [] + self.desc = desc + self.obsolete = obsolete + self.applies = applies or [] + self.extensions = extensions or {} + + def __str__(self): + tokens = ['(', self.oid] + if self.name: + tokens += ['NAME'] + qdescrs_to_tokens(self.name) + if self.desc: + tokens += ['DESC', qdstring_to_token(self.desc)] + if self.obsolete: + tokens += ['OBSOLETE'] + tokens += ['APPLIES'] + oids_to_tokens(self.applies) + tokens += extensions_to_tokens(self.extensions) + [')'] + return ' '.join(tokens) + + @property + def first_component_oid(self): + return self.oid + + +class AttributeTypeUsage(enum.Enum): + '''Values for usage argument of `AttributeTypeUsage`''' + # pylint: disable=invalid-name + # user + userApplications = enum.auto() + # directory operational + directoryOperation = enum.auto() + # DSA-shared operational + distributedOperation = enum.auto() + # DSA-specific operational + dSAOperation = enum.auto() + +class AttributeTypeDefinition: + # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-locals + def __init__(self, oid, name=None, desc='', obsolete=False, sup=None, + equality=None, ordering=None, substr=None, syntax=None, + syntax_len=None, single_value=False, collective=False, + no_user_modification=False, + usage=AttributeTypeUsage.userApplications, extensions=None): + if not sup and not syntax: + raise ValueError('Either SUP or SYNTAX must be specified') + self.oid = oid + self.name = name or [] + self.desc = desc + self.obsolete = obsolete + self.sup = sup + self.equality = equality + self.ordering = ordering + self.substr = substr + self.syntax = syntax + self.syntax_len = syntax_len + self.single_value = single_value + self.collective = collective + self.no_user_modification = no_user_modification + self.usage = usage + self.extensions = extensions or {} + + def __str__(self): + tokens = ['(', self.oid] + if self.name: + tokens += ['NAME'] + qdescrs_to_tokens(self.name) + if self.desc: + tokens += ['DESC', qdstring_to_token(self.desc)] + if self.obsolete: + tokens += ['OBSOLETE'] + if self.sup: + tokens += ['SUP', self.sup] + if self.equality: + tokens += ['EQUALITY', self.equality] + if self.ordering: + tokens += ['ORDERING', self.ordering] + if self.substr: + tokens += ['SUBSTR', self.substr] + if self.syntax: + if self.syntax_len is None: + tokens += ['SYNTAX', self.syntax] + else: + tokens += ['SYNTAX', self.syntax+'{'+str(self.syntax_len)+'}'] + if self.single_value: + tokens += ['SINGLE-VALUE'] + if self.collective: + tokens += ['COLLECTIVE'] + if self.no_user_modification: + tokens += ['NO-USER-MODIFICATION'] + if self.usage != AttributeTypeUsage.userApplications: + tokens += ['USAGE', self.usage.name] + tokens += extensions_to_tokens(self.extensions) + [')'] + return ' '.join(tokens) + + @classmethod + def from_str(cls, string): + tokens = tokenize(string) + parse_token(tokens, '(') + oid = parse_numericoid(tokens) + name = [] + if check_token(tokens, 'NAME'): + name = parse_qdescrs(tokens) + desc = '' + if check_token(tokens, 'DESC'): + desc = parse_qdstring(tokens) + obsolete = check_token(tokens, 'OBSOLETE') + sup = None + if check_token(tokens, 'SUP'): + sup = parse_oid(tokens) + equality = None + if check_token(tokens, 'EQUALITY'): + equality = parse_oid(tokens) + ordering = None + if check_token(tokens, 'ORDERING'): + ordering = parse_oid(tokens) + substr = None + if check_token(tokens, 'SUBSTR'): + substr = parse_oid(tokens) + syntax, syntax_len = None, None + if check_token(tokens, 'SYNTAX'): + syntax, syntax_len = parse_noidlen(tokens) + single_value = check_token(tokens, 'SINGLE-VALUE') + collective = check_token(tokens, 'COLLECTIVE') + no_user_modification = check_token(tokens, 'NO-USER-MODIFICATION') + usage = AttributeTypeUsage.userApplications + if check_token(tokens, 'USAGE'): + token = tokens.pop(0) + if token == 'userApplications': + usage = AttributeTypeUsage.userApplications + elif token == 'directoryOperation': + usage = AttributeTypeUsage.directoryOperation + elif token == 'distributedOperation': + usage = AttributeTypeUsage.distributedOperation + elif token == 'dSAOperation': + usage = AttributeTypeUsage.dSAOperation + else: + raise ValueError(f'Invalid usage value "{token}"') + extensions = parse_extensions(tokens) + parse_token(tokens, ')') + if tokens: + raise ValueError(f'Unexpected token "{tokens[0]}", expected no more input') + return cls(oid, name=name, desc=desc, obsolete=obsolete, sup=sup, + equality=equality, ordering=ordering, substr=substr, + syntax=syntax, syntax_len=syntax_len, single_value=single_value, + collective=collective, no_user_modification=no_user_modification, + usage=usage, extensions=extensions) + + @property + def first_component_oid(self): + return self.oid + +class ObjectClassKind(enum.Enum): + '''Values for kind argument of `ObjectClass`''' + ABSTRACT = enum.auto() + STRUCTURAL = enum.auto() + AUXILIARY = enum.auto() + +class ObjectClassDefinition: + # pylint: disable=too-many-arguments + def __init__(self, oid, name=None, desc='', obsolete=False, sup=None, + kind=ObjectClassKind.STRUCTURAL, must=None, may=None, + extensions=None): + self.oid = oid + self.name = name or [] + self.desc = desc + self.obsolete = obsolete + self.sup = sup or [] + self.kind = kind + self.must = must or [] + self.may = may or [] + self.extensions = extensions or {} + + def __str__(self): + tokens = ['(', self.oid] + if self.name: + tokens += ['NAME'] + qdescrs_to_tokens(self.name) + if self.desc: + tokens += ['DESC', qdstring_to_token(self.desc)] + if self.obsolete: + tokens += ['OBSOLETE'] + if self.sup: + tokens += ['SUP'] + oids_to_tokens(self.sup) + tokens += [self.kind.name] + if self.must: + tokens += ['MUST'] + oids_to_tokens(self.must) + if self.may: + tokens += ['MAY'] + oids_to_tokens(self.may) + tokens += extensions_to_tokens(self.extensions) + [')'] + return ' '.join(tokens) + + @classmethod + def from_str(cls, string): + tokens = tokenize(string) + parse_token(tokens, '(') + oid = parse_numericoid(tokens) + name = [] + if check_token(tokens, 'NAME'): + name = parse_qdescrs(tokens) + desc = '' + if check_token(tokens, 'DESC'): + desc = parse_qdstring(tokens) + obsolete = check_token(tokens, 'OBSOLETE') + sup = [] + if check_token(tokens, 'SUP'): + sup = parse_oids(tokens) + kind = ObjectClassKind.STRUCTURAL + if check_token(tokens, 'ABSTRACT'): + kind = ObjectClassKind.ABSTRACT + elif check_token(tokens, 'STRUCTURAL'): + kind = ObjectClassKind.STRUCTURAL + elif check_token(tokens, 'AUXILIARY'): + kind = ObjectClassKind.AUXILIARY + must = [] + if check_token(tokens, 'MUST'): + must = parse_oids(tokens) + may = [] + if check_token(tokens, 'MAY'): + may = parse_oids(tokens) + extensions = parse_extensions(tokens) + parse_token(tokens, ')') + if tokens: + raise ValueError(f'Unexpected token "{tokens[0]}", expected no more input') + return cls(oid, name=name, desc=desc, obsolete=obsolete, sup=sup, + kind=kind, must=must, may=may, extensions=extensions) + + @property + def first_component_oid(self): + return self.oid diff --git a/ldapserver/schema/matching_rules.py b/ldapserver/schema/matching_rules.py new file mode 100644 index 0000000000000000000000000000000000000000..851eb6f759e7d15ad82777711b0e85be7ee6eb6b --- /dev/null +++ b/ldapserver/schema/matching_rules.py @@ -0,0 +1,267 @@ +import re + +from .definitions import MatchingRuleDefinition, MatchingRuleKind +from .. import rfc4518_stringprep, exceptions +from . import syntaxes + +class GenericMatchingRuleDefinition(MatchingRuleDefinition): + def match_equal(self, schema, attribute_values, assertion_value): + for attribute_value in attribute_values: + if attribute_value == assertion_value: + return True + return False + + def match_less(self, schema, attribute_values, assertion_value): + for attribute_value in attribute_values: + if attribute_value < assertion_value: + return True + return False + + def match_greater_or_equal(self, schema, attribute_values, assertion_value): + for attribute_value in attribute_values: + if attribute_value >= assertion_value: + return True + return False + +def _substr_match(attribute_value, inital_substring, any_substrings, final_substring): + if inital_substring: + if not attribute_value.startswith(inital_substring): + return False + attribute_value = attribute_value[len(inital_substring):] + if final_substring: + if not attribute_value.endswith(final_substring): + return False + attribute_value = attribute_value[:-len(final_substring)] + for substring in any_substrings: + index = attribute_value.find(substring) + if index == -1: + return False + attribute_value = attribute_value[index+len(substring):] + return True + +class StringMatchingRuleDefinition(MatchingRuleDefinition): + def __init__(self, oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, **kwargs): + super().__init__(oid, **kwargs) + self.matching_type = matching_type + + def prepare_assertion_value(self, attribute_value): + try: + return rfc4518_stringprep.prepare(attribute_value, self.matching_type) + except ValueError as exc: + raise exceptions.LDAPInvalidAttributeSyntax('Assertion value contains characters prohibited by RFC4518') from exc + + def prepare_attribute_value(self, attribute_value): + try: + return rfc4518_stringprep.prepare(attribute_value, self.matching_type) + except ValueError: + return None + + def match_equal(self, schema, attribute_values, assertion_value): + assertion_value = self.prepare_assertion_value(assertion_value) + for attribute_value in attribute_values: + attribute_value = self.prepare_attribute_value(attribute_value) + if attribute_value == assertion_value: + return True + return False + + def match_less(self, schema, attribute_values, assertion_value): + assertion_value = self.prepare_assertion_value(assertion_value) + for attribute_value in attribute_values: + attribute_value = self.prepare_attribute_value(attribute_value) + if attribute_value < assertion_value: + return True + return False + + def match_greater_or_equal(self, schema, attribute_values, assertion_value): + assertion_value = self.prepare_assertion_value(assertion_value) + for attribute_value in attribute_values: + attribute_value = self.prepare_attribute_value(attribute_value) + if attribute_value >= assertion_value: + return True + return False + + def match_substr(self, schema, attribute_values, inital_substring, any_substrings, final_substring): + try: + if inital_substring: + inital_substring = rfc4518_stringprep.prepare(inital_substring, self.matching_type, rfc4518_stringprep.SubstringType.INITIAL) + any_substrings = [rfc4518_stringprep.prepare(substring, self.matching_type, rfc4518_stringprep.SubstringType.ANY) for substring in any_substrings] + if final_substring: + final_substring = rfc4518_stringprep.prepare(final_substring, self.matching_type, rfc4518_stringprep.SubstringType.FINAL) + except ValueError as exc: + raise exceptions.LDAPInvalidAttributeSyntax('Assertion value contains characters prohibited by RFC4518') from exc + for attribute_value in attribute_values: + try: + attribute_value = self.prepare_attribute_value(attribute_value) + if _substr_match(attribute_value, inital_substring, any_substrings, final_substring): + return True + except ValueError: + pass + return False + +class StringListMatchingRuleDefinition(MatchingRuleDefinition): + def __init__(self, oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, **kwargs): + super().__init__(oid, **kwargs) + self.matching_type = matching_type + + # Values are both lists of str + def match_equal(self, schema, attribute_values, assertion_value): + try: + assertion_value = [rfc4518_stringprep.prepare(line, self.matching_type) for line in assertion_value] + except ValueError as exc: + raise exceptions.LDAPInvalidAttributeSyntax('Assertion value contains characters prohibited by RFC4518') from exc + for attribute_value in attribute_values: + try: + attribute_value = [rfc4518_stringprep.prepare(line, self.matching_type) for line in attribute_value] + if attribute_value == assertion_value: + return True + except ValueError: + pass + return False + + def match_substr(self, schema, attribute_values, inital_substring, any_substrings, final_substring): + try: + if inital_substring: + inital_substring = rfc4518_stringprep.prepare(inital_substring, self.matching_type, rfc4518_stringprep.SubstringType.INITIAL) + any_substrings = [rfc4518_stringprep.prepare(substring, self.matching_type, rfc4518_stringprep.SubstringType.ANY) for substring in any_substrings] + if final_substring: + final_substring = rfc4518_stringprep.prepare(final_substring, self.matching_type, rfc4518_stringprep.SubstringType.FINAL) + except ValueError as exc: + raise exceptions.LDAPInvalidAttributeSyntax('Assertion value contains characters prohibited by RFC4518') from exc + for attribute_value in attribute_values: + try: + # LF is mapped to SPACE by stringprep, so it is suitable as a seperator + attribute_value = '\n'.join([rfc4518_stringprep.prepare(line, self.matching_type) for line in attribute_value]) + if _substr_match(attribute_value, inital_substring, any_substrings, final_substring): + return True + except ValueError: + pass + return False + +class FirstComponentMatchingRuleDefinition(MatchingRuleDefinition): + def __init__(self, oid, attribute_name, matching_rule, compatability_tag=None, **kwargs): + compatability_tag = compatability_tag or 'FirstComponent:'+matching_rule.compatability_tag + super().__init__(oid, compatability_tag=compatability_tag, **kwargs) + self.attribute_name = attribute_name + self.matching_rule = matching_rule + + def match_equal(self, schema, attribute_values, assertion_value): + attribute_values = [getattr(value, self.attribute_name) + for value in attribute_values + if hasattr(value, self.attribute_name)] + return self.matching_rule.match_equal(schema, attribute_values, assertion_value) + + +class OIDMatchingRuleDefinition(MatchingRuleDefinition): + NUMERIC_OID_RE = re.compile(r"([0-9]|[1-9][0-9]*)(\.([0-9]|[1-9][0-9]*))*") + + def match_equal(self, schema, attribute_values, assertion_value): + if not self.NUMERIC_OID_RE.fullmatch(assertion_value): + assertion_value = schema.get_numeric_oid(assertion_value) + if assertion_value is None: + raise exceptions.LDAPInvalidAttributeSyntax('Assertion value is an unknown OID descriptor') + for attribute_value in attribute_values: + attribute_value = schema.get_numeric_oid(attribute_value, attribute_value) + if attribute_value == assertion_value: + return True + return False + +class StubMatchingRuleDefinition(MatchingRuleDefinition): + pass + +# RFC4517 +bitStringMatch = GenericMatchingRuleDefinition('2.5.13.16', name=['bitStringMatch'], syntax=syntaxes.BitString.oid, kind=MatchingRuleKind.EQUALITY) +booleanMatch = GenericMatchingRuleDefinition('2.5.13.13', name=['booleanMatch'], syntax=syntaxes.Boolean.oid, kind=MatchingRuleKind.EQUALITY) +caseExactIA5Match = StringMatchingRuleDefinition('1.3.6.1.4.1.1466.109.114.1', name=['caseExactIA5Match'], syntax=syntaxes.IA5String.oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, kind=MatchingRuleKind.EQUALITY) +caseExactMatch = StringMatchingRuleDefinition('2.5.13.5', name=['caseExactMatch'], syntax=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, kind=MatchingRuleKind.EQUALITY) +caseExactOrderingMatch = StringMatchingRuleDefinition('2.5.13.6', name=['caseExactOrderingMatch'], syntax=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, kind=MatchingRuleKind.ORDERING) +caseExactSubstringsMatch = StringMatchingRuleDefinition('2.5.13.7', name=['caseExactSubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING, kind=MatchingRuleKind.SUBSTR) +caseIgnoreIA5Match = StringMatchingRuleDefinition('1.3.6.1.4.1.1466.109.114.2', name=['caseIgnoreIA5Match'], syntax=syntaxes.IA5String.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.EQUALITY) +caseIgnoreIA5SubstringsMatch = StringMatchingRuleDefinition('1.3.6.1.4.1.1466.109.114.3', name=['caseIgnoreIA5SubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.IA5String.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.SUBSTR) +caseIgnoreListMatch = StringListMatchingRuleDefinition('2.5.13.11', name=['caseIgnoreListMatch'], syntax=syntaxes.PostalAddress.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.EQUALITY) +caseIgnoreListSubstringsMatch = StringListMatchingRuleDefinition('2.5.13.12', name=['caseIgnoreListSubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.PostalAddress.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.SUBSTR) +caseIgnoreMatch = StringMatchingRuleDefinition('2.5.13.2', name=['caseIgnoreMatch'], syntax=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.EQUALITY) +caseIgnoreOrderingMatch = StringMatchingRuleDefinition('2.5.13.3', name=['caseIgnoreOrderingMatch'], syntax=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.ORDERING) +caseIgnoreSubstringsMatch = StringMatchingRuleDefinition('2.5.13.4', name=['caseIgnoreSubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.DirectoryString.oid, matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING, kind=MatchingRuleKind.SUBSTR) +directoryStringFirstComponentMatch = FirstComponentMatchingRuleDefinition('2.5.13.31', name=['directoryStringFirstComponentMatch'], syntax=syntaxes.DirectoryString.oid, attribute_name='first_component_string', matching_rule=caseIgnoreMatch, kind=MatchingRuleKind.EQUALITY) +distinguishedNameMatch = GenericMatchingRuleDefinition('2.5.13.1', name=['distinguishedNameMatch'], syntax=syntaxes.DN.oid, kind=MatchingRuleKind.EQUALITY) +generalizedTimeMatch = GenericMatchingRuleDefinition('2.5.13.27', name=['generalizedTimeMatch'], syntax=syntaxes.GeneralizedTime.oid, kind=MatchingRuleKind.EQUALITY) +generalizedTimeOrderingMatch = GenericMatchingRuleDefinition('2.5.13.28', name=['generalizedTimeOrderingMatch'], syntax=syntaxes.GeneralizedTime.oid, kind=MatchingRuleKind.ORDERING) +integerMatch = GenericMatchingRuleDefinition('2.5.13.14', name=['integerMatch'], syntax=syntaxes.INTEGER.oid, kind=MatchingRuleKind.EQUALITY) +integerFirstComponentMatch = FirstComponentMatchingRuleDefinition('2.5.13.29', name=['integerFirstComponentMatch'], syntax=syntaxes.INTEGER.oid, attribute_name='first_component_integer', matching_rule=integerMatch, kind=MatchingRuleKind.EQUALITY) +integerOrderingMatch = GenericMatchingRuleDefinition('2.5.13.15', name=['integerOrderingMatch'], syntax=syntaxes.INTEGER.oid, kind=MatchingRuleKind.ORDERING) +# Optional and implementation-specific, we simply never match +keywordMatch = StubMatchingRuleDefinition('2.5.13.33', name=['keywordMatch'], syntax=syntaxes.DirectoryString.oid, kind=MatchingRuleKind.EQUALITY) +numericStringMatch = StringMatchingRuleDefinition('2.5.13.8', name=['numericStringMatch'], syntax=syntaxes.NumericString.oid, matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING, kind=MatchingRuleKind.EQUALITY) +numericStringOrderingMatch = StringMatchingRuleDefinition('2.5.13.9', name=['numericStringOrderingMatch'], syntax=syntaxes.NumericString.oid, matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING, kind=MatchingRuleKind.ORDERING) +numericStringSubstringsMatch = StringMatchingRuleDefinition('2.5.13.10', name=['numericStringSubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.NumericString.oid, matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING, kind=MatchingRuleKind.SUBSTR) +objectIdentifierMatch = OIDMatchingRuleDefinition('2.5.13.0', name=['objectIdentifierMatch'], syntax=syntaxes.OID.oid, kind=MatchingRuleKind.EQUALITY) +objectIdentifierFirstComponentMatch = FirstComponentMatchingRuleDefinition('2.5.13.30', name=['objectIdentifierFirstComponentMatch'], syntax=syntaxes.OID.oid, attribute_name='first_component_oid', matching_rule=objectIdentifierMatch, kind=MatchingRuleKind.EQUALITY) +octetStringMatch = GenericMatchingRuleDefinition('2.5.13.17', name=['octetStringMatch'], syntax=syntaxes.OctetString.oid, kind=MatchingRuleKind.EQUALITY) +octetStringOrderingMatch = GenericMatchingRuleDefinition('2.5.13.18', name=['octetStringOrderingMatch'], syntax=syntaxes.OctetString.oid, kind=MatchingRuleKind.ORDERING) +telephoneNumberMatch = StringMatchingRuleDefinition('2.5.13.20', name=['telephoneNumberMatch'], syntax=syntaxes.TelephoneNumber.oid, matching_type=rfc4518_stringprep.MatchingType.TELEPHONE_NUMBER, kind=MatchingRuleKind.EQUALITY) +telephoneNumberSubstringsMatch = StringMatchingRuleDefinition('2.5.13.21', name=['telephoneNumberSubstringsMatch'], syntax=syntaxes.SubstringAssertion.oid, compatability_tag=syntaxes.TelephoneNumber.oid, matching_type=rfc4518_stringprep.MatchingType.TELEPHONE_NUMBER, kind=MatchingRuleKind.SUBSTR) +uniqueMemberMatch = GenericMatchingRuleDefinition('2.5.13.23', name=['uniqueMemberMatch'], syntax=syntaxes.NameAndOptionalUID.oid, kind=MatchingRuleKind.EQUALITY) +# Optional and implementation-specific, we simply never match +wordMatch = StubMatchingRuleDefinition('2.5.13.32', name=['wordMatch'], syntax=syntaxes.DirectoryString.oid, kind=MatchingRuleKind.EQUALITY) + +# RFC4523 +certificateExactMatch = StubMatchingRuleDefinition('2.5.13.34', name=['certificateExactMatch'], desc='X.509 Certificate Exact Match', syntax='1.3.6.1.1.15.1', kind=MatchingRuleKind.EQUALITY) +certificateMatch = StubMatchingRuleDefinition('2.5.13.35', name=['certificateMatch'], desc='X.509 Certificate Match', syntax='1.3.6.1.1.15.2', kind=MatchingRuleKind.EQUALITY) +certificatePairExactMatch = StubMatchingRuleDefinition('2.5.13.36', name=['certificatePairExactMatch'], desc='X.509 Certificate Pair Exact Match', syntax='1.3.6.1.1.15.3', kind=MatchingRuleKind.EQUALITY) +certificatePairMatch = StubMatchingRuleDefinition('2.5.13.37', name=['certificatePairMatch'], desc='X.509 Certificate Pair Match', syntax='1.3.6.1.1.15.4', kind=MatchingRuleKind.EQUALITY) +certificateListExactMatch = StubMatchingRuleDefinition('2.5.13.38', name=['certificateListExactMatch'], desc='X.509 Certificate List Exact Match', syntax='1.3.6.1.1.15.5', kind=MatchingRuleKind.EQUALITY) +certificateListMatch = StubMatchingRuleDefinition('2.5.13.39', name=['certificateListMatch'], desc='X.509 Certificate List Match', syntax='1.3.6.1.1.15.6', kind=MatchingRuleKind.EQUALITY) +algorithmIdentifierMatch = StubMatchingRuleDefinition('2.5.13.40', name=['algorithmIdentifierMatch'], desc='X.509 Algorithm Identifier Match', syntax='1.3.6.1.1.15.7', kind=MatchingRuleKind.EQUALITY) + +# RFC3112 +authPasswordExactMatch = StubMatchingRuleDefinition('1.3.6.1.4.1.4203.1.2.2', name=['authPasswordExactMatch'], desc='authentication password exact matching rule', syntax='1.3.6.1.4.1.1466.115.121.1.40', kind=MatchingRuleKind.EQUALITY) +authPasswordMatch = StubMatchingRuleDefinition('1.3.6.1.4.1.4203.1.2.3', name=['authPasswordMatch'], desc='authentication password matching rule', syntax='1.3.6.1.4.1.1466.115.121.1.40', kind=MatchingRuleKind.EQUALITY) + +ALL = ( + # RFC4517 + bitStringMatch, + booleanMatch, + caseExactIA5Match, + caseExactMatch, + caseExactOrderingMatch, + caseExactSubstringsMatch, + caseIgnoreIA5Match, + caseIgnoreIA5SubstringsMatch, + caseIgnoreListMatch, + caseIgnoreListSubstringsMatch, + caseIgnoreMatch, + caseIgnoreOrderingMatch, + caseIgnoreSubstringsMatch, + directoryStringFirstComponentMatch, + distinguishedNameMatch, + generalizedTimeMatch, + generalizedTimeOrderingMatch, + integerFirstComponentMatch, + integerMatch, + integerOrderingMatch, + keywordMatch, + numericStringMatch, + numericStringOrderingMatch, + numericStringSubstringsMatch, + objectIdentifierFirstComponentMatch, + objectIdentifierMatch, + octetStringMatch, + octetStringOrderingMatch, + telephoneNumberMatch, + telephoneNumberSubstringsMatch, + uniqueMemberMatch, + wordMatch, + + # RFC4523 + certificateExactMatch, + certificateMatch, + certificatePairExactMatch, + certificatePairMatch, + certificateListExactMatch, + certificateListMatch, + algorithmIdentifierMatch, + + # RFC3112 + authPasswordExactMatch, + authPasswordMatch, +) diff --git a/ldapserver/schema/rfc1274/__init__.py b/ldapserver/schema/rfc1274/__init__.py deleted file mode 100644 index 15af5069f5718d05686bc98efb560b97a4006a02..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc1274/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, attribute_types diff --git a/ldapserver/schema/rfc1274/attribute_types.py b/ldapserver/schema/rfc1274/attribute_types.py deleted file mode 100644 index c2d5c1246df0c6a6945d47c3e2c6acea62732e03..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc1274/attribute_types.py +++ /dev/null @@ -1,10 +0,0 @@ -from ..types import AttributeType -from . import syntaxes - -audio = AttributeType('0.9.2342.19200300.100.1.55', name='audio', desc='audio (u-law)', syntax=syntaxes.OctetString(25000)) -photo = AttributeType('0.9.2342.19200300.100.1.7', name='photo', desc='photo (G3 fax)', syntax=syntaxes.OctetString(25000)) - -ALL = ( - audio, - photo, -) diff --git a/ldapserver/schema/rfc1274/syntaxes.py b/ldapserver/schema/rfc1274/syntaxes.py deleted file mode 100644 index c03df67ab8c2ad9691c5ca9ab4b12d067bccbd6a..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc1274/syntaxes.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..rfc4517.syntaxes import OctetString - -ALL = ( - OctetString, -) diff --git a/ldapserver/schema/rfc2079/__init__.py b/ldapserver/schema/rfc2079/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2079/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc2079/attribute_types.py b/ldapserver/schema/rfc2079/attribute_types.py deleted file mode 100644 index 4991c5a3a056ba5a3f8753e924a03a3d725088c4..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2079/attribute_types.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..types import AttributeType -from . import syntaxes, matching_rules - -labeledURI = AttributeType('1.3.6.1.4.1.250.1.57', name='labeledURI', desc='Uniform Resource Identifier with optional label', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString()) - -ALL = ( - labeledURI, -) diff --git a/ldapserver/schema/rfc2079/matching_rules.py b/ldapserver/schema/rfc2079/matching_rules.py deleted file mode 100644 index 77afc5bb9716470bcd0a2481b1d5fa116e14b2e2..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2079/matching_rules.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..rfc4517.matching_rules import caseExactMatch - -ALL = ( - caseExactMatch, -) diff --git a/ldapserver/schema/rfc2079/object_classes.py b/ldapserver/schema/rfc2079/object_classes.py deleted file mode 100644 index 0254395b0aadd0284031fad67c0210008ed751ae..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2079/object_classes.py +++ /dev/null @@ -1,9 +0,0 @@ -from ..types import ObjectClass, ObjectClassKind -from ..rfc4512.object_classes import top -from . import attribute_types - -labeledURIObject = ObjectClass('1.3.6.1.4.1.250.3.15', name='labeledURIObject', desc='object that contains the URI attribute type', sup=top, kind=ObjectClassKind.AUXILIARY, may=[attribute_types.labeledURI]) - -ALL = ( - labeledURIObject, -) diff --git a/ldapserver/schema/rfc2079/syntaxes.py b/ldapserver/schema/rfc2079/syntaxes.py deleted file mode 100644 index 5d68139c5fa7703f7bd52035ad0c85784f25a401..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2079/syntaxes.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..rfc4517.syntaxes import DirectoryString - -ALL = ( - DirectoryString, -) diff --git a/ldapserver/schema/rfc2252/__init__.py b/ldapserver/schema/rfc2252/__init__.py deleted file mode 100644 index 2b160bc1ac78b9e011eb5ca457b0f858f30257b1..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2252/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes diff --git a/ldapserver/schema/rfc2252/syntaxes.py b/ldapserver/schema/rfc2252/syntaxes.py deleted file mode 100644 index cf2d2492dd021dfe34c5d69453aa0862db34de15..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2252/syntaxes.py +++ /dev/null @@ -1,19 +0,0 @@ -from ..types import Syntax - -# Only deprecated syntaxes from the old LDAP v3 RFCs - -class Binary(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.5' - desc = 'Binary' - - @staticmethod - def encode(schema, value): - return value - - @staticmethod - def decode(schema, raw_value): - return raw_value - -ALL = ( - Binary, -) diff --git a/ldapserver/schema/rfc2307bis/__init__.py b/ldapserver/schema/rfc2307bis/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2307bis/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc2307bis/attribute_types.py b/ldapserver/schema/rfc2307bis/attribute_types.py deleted file mode 100644 index 9cca89031364155e65aa6f2848534abe124e5663..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2307bis/attribute_types.py +++ /dev/null @@ -1,77 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import,unused-import - -from ..types import AttributeType -from ..rfc4524.attribute_types import * -from ..rfc3112.attribute_types import authPassword, ALL as RFC3112_ALL -from . import syntaxes, matching_rules - -uidNumber = AttributeType('1.3.6.1.1.1.1.0', name='uidNumber', desc='An integer uniquely identifying a user in an administrative domain', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -gidNumber = AttributeType('1.3.6.1.1.1.1.1', name='gidNumber', desc='An integer uniquely identifying a group in an administrative domain', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -gecos = AttributeType('1.3.6.1.1.1.1.2', name='gecos', desc='The GECOS field; the common name', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(), single_value=True) -homeDirectory = AttributeType('1.3.6.1.1.1.1.3', name='homeDirectory', desc='The absolute path to the home directory', equality=matching_rules.caseExactIA5Match, syntax=syntaxes.IA5String(), single_value=True) -loginShell = AttributeType('1.3.6.1.1.1.1.4', name='loginShell', desc='The path to the login shell', equality=matching_rules.caseExactIA5Match, syntax=syntaxes.IA5String(), single_value=True) -shadowLastChange = AttributeType('1.3.6.1.1.1.1.5', name='shadowLastChange', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowMin = AttributeType('1.3.6.1.1.1.1.6', name='shadowMin', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowMax = AttributeType('1.3.6.1.1.1.1.7', name='shadowMax', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowWarning = AttributeType('1.3.6.1.1.1.1.8', name='shadowWarning', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowInactive = AttributeType('1.3.6.1.1.1.1.9', name='shadowInactive', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowExpire = AttributeType('1.3.6.1.1.1.1.10', name='shadowExpire', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -shadowFlag = AttributeType('1.3.6.1.1.1.1.11', name='shadowFlag', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -memberUid = AttributeType('1.3.6.1.1.1.1.12', name='memberUid', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString()) -memberNisNetgroup = AttributeType('1.3.6.1.1.1.1.13', name='memberNisNetgroup', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString()) -nisNetgroupTriple = AttributeType('1.3.6.1.1.1.1.14', name='nisNetgroupTriple', desc='Netgroup triple', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -ipServicePort = AttributeType('1.3.6.1.1.1.1.15', name='ipServicePort', desc='Service port number', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -ipServiceProtocol = AttributeType('1.3.6.1.1.1.1.16', name='ipServiceProtocol', desc='Service protocol name', equality=matching_rules.caseIgnoreMatch, syntax=syntaxes.DirectoryString()) -ipProtocolNumber = AttributeType('1.3.6.1.1.1.1.17', name='ipProtocolNumber', desc='IP protocol number', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -oncRpcNumber = AttributeType('1.3.6.1.1.1.1.18', name='oncRpcNumber', desc='ONC RPC number', equality=matching_rules.integerMatch, ordering=matching_rules.integerOrderingMatch, syntax=syntaxes.INTEGER(), single_value=True) -ipHostNumber = AttributeType('1.3.6.1.1.1.1.19', name='ipHostNumber', desc='IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC2373', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String()) -ipNetworkNumber = AttributeType('1.3.6.1.1.1.1.20', name='ipNetworkNumber', desc='IP network omitting leading zeros, eg. 192.168', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String(), single_value=True) -ipNetmaskNumber = AttributeType('1.3.6.1.1.1.1.21', name='ipNetmaskNumber', desc='IP netmask omitting leading zeros, eg. 255.255.255.0', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String(), single_value=True) -macAddress = AttributeType('1.3.6.1.1.1.1.22', name='macAddress', desc='MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String()) -bootParameter = AttributeType('1.3.6.1.1.1.1.23', name='bootParameter', desc='rpc.bootparamd parameter', equality=matching_rules.caseExactIA5Match, syntax=syntaxes.IA5String()) -bootFile = AttributeType('1.3.6.1.1.1.1.24', name='bootFile', desc='Boot image name', equality=matching_rules.caseExactIA5Match, syntax=syntaxes.IA5String()) -nisMapName = AttributeType('1.3.6.1.1.1.1.26', name='nisMapName', desc='Name of a generic NIS map', equality=matching_rules.caseIgnoreMatch, syntax=syntaxes.DirectoryString(64)) -nisMapEntry = AttributeType('1.3.6.1.1.1.1.27', name='nisMapEntry', desc='A generic NIS entry', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString(1024), single_value=True) -nisPublicKey = AttributeType('1.3.6.1.1.1.1.28', name='nisPublicKey', desc='NIS public key', equality=matching_rules.octetStringMatch, syntax=syntaxes.OctetString(), single_value=True) -nisSecretKey = AttributeType('1.3.6.1.1.1.1.29', name='nisSecretKey', desc='NIS secret key', equality=matching_rules.octetStringMatch, syntax=syntaxes.OctetString(), single_value=True) -nisDomain = AttributeType('1.3.6.1.1.1.1.30', name='nisDomain', desc='NIS domain', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String(256)) -automountMapName = AttributeType('1.3.6.1.1.1.1.31', name='automountMapName', desc='automount Map Name', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString(), single_value=True) -automountKey = AttributeType('1.3.6.1.1.1.1.32', name='automountKey', desc='Automount Key value', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString(), single_value=True) -automountInformation = AttributeType('1.3.6.1.1.1.1.33', name='automountInformation', desc='Automount information', equality=matching_rules.caseExactMatch, syntax=syntaxes.DirectoryString(), single_value=True) - -ALL = ALL + RFC3112_ALL + ( - uidNumber, - gidNumber, - gecos, - homeDirectory, - loginShell, - shadowLastChange, - shadowMin, - shadowMax, - shadowWarning, - shadowInactive, - shadowExpire, - shadowFlag, - memberUid, - memberNisNetgroup, - nisNetgroupTriple, - ipServicePort, - ipServiceProtocol, - ipProtocolNumber, - oncRpcNumber, - ipHostNumber, - ipNetworkNumber, - ipNetmaskNumber, - macAddress, - bootParameter, - bootFile, - nisMapName, - nisMapEntry, - nisPublicKey, - nisSecretKey, - nisDomain, - automountMapName, - automountKey, - automountInformation, -) diff --git a/ldapserver/schema/rfc2307bis/matching_rules.py b/ldapserver/schema/rfc2307bis/matching_rules.py deleted file mode 100644 index adeaf47846621a693be1f49fb208577e489ca70b..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2307bis/matching_rules.py +++ /dev/null @@ -1,7 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4524.matching_rules import * -from ..rfc3112.matching_rules import ALL as RFC3112_ALL - -ALL = ALL + RFC3112_ALL diff --git a/ldapserver/schema/rfc2307bis/object_classes.py b/ldapserver/schema/rfc2307bis/object_classes.py deleted file mode 100644 index f61e7d2c93e14a37a52fa7c5a2100812e3ac0301..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2307bis/object_classes.py +++ /dev/null @@ -1,47 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import ObjectClass, ObjectClassKind -from ..rfc3112.object_classes import ALL as RFC3112_ALL -from ..rfc4524.object_classes import * -from . import attribute_types - -posixAccount = ObjectClass('1.3.6.1.1.1.2.0', name='posixAccount', sup=top, kind=ObjectClassKind.AUXILIARY, desc='Abstraction of an account with POSIX attributes', must=[attribute_types.cn, attribute_types.uid, attribute_types.uidNumber, attribute_types.gidNumber, attribute_types.homeDirectory], may=[attribute_types.authPassword, attribute_types.userPassword, attribute_types.loginShell, attribute_types.gecos, attribute_types.description]) -shadowAccount = ObjectClass('1.3.6.1.1.1.2.1', name='shadowAccount', sup=top, kind=ObjectClassKind.AUXILIARY, desc='Additional attributes for shadow passwords', must=[attribute_types.uid], may=[attribute_types.authPassword, attribute_types.userPassword, attribute_types.description, attribute_types.shadowLastChange, attribute_types.shadowMin, attribute_types.shadowMax, attribute_types.shadowWarning, attribute_types.shadowInactive, attribute_types.shadowExpire, attribute_types.shadowFlag]) -posixGroup = ObjectClass('1.3.6.1.1.1.2.2', name='posixGroup', sup=top, kind=ObjectClassKind.AUXILIARY, desc='Abstraction of a group of accounts', must=[attribute_types.gidNumber], may=[attribute_types.authPassword, attribute_types.userPassword, attribute_types.memberUid, attribute_types.description]) -ipService = ObjectClass('1.3.6.1.1.1.2.3', name='ipService', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names; the distinguished value of the cn attribute denotes the service\'s canonical name', must=[attribute_types.cn, attribute_types.ipServicePort, attribute_types.ipServiceProtocol], may=[attribute_types.description]) -ipProtocol = ObjectClass('1.3.6.1.1.1.2.4', name='ipProtocol', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Abstraction of an IP protocol. Maps a protocol number to one or more names. The distinguished value of the cn attribute denotes the protocol canonical name', must=[attribute_types.cn, attribute_types.ipProtocolNumber], may=[attribute_types.description]) -oncRpc = ObjectClass('1.3.6.1.1.1.2.5', name='oncRpc', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Abstraction of an Open Network Computing (ONC) [RFC1057] Remote Procedure Call (RPC) binding. This class maps an ONC RPC number to a name. The distinguished value of the cn attribute denotes the RPC service canonical name', must=[attribute_types.cn, attribute_types.oncRpcNumber], may=[attribute_types.description]) -ipHost = ObjectClass('1.3.6.1.1.1.2.6', name='ipHost', sup=top, kind=ObjectClassKind.AUXILIARY, desc='Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the host\'s canonical name. Device SHOULD be used as a structural class', must=[attribute_types.cn, attribute_types.ipHostNumber], may=[attribute_types.authPassword, attribute_types.userPassword, attribute_types.l, attribute_types.description, attribute_types.manager]) -ipNetwork = ObjectClass('1.3.6.1.1.1.2.7', name='ipNetwork', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Abstraction of a network. The distinguished value of the cn attribute denotes the network canonical name', must=[attribute_types.ipNetworkNumber], may=[attribute_types.cn, attribute_types.ipNetmaskNumber, attribute_types.l, attribute_types.description, attribute_types.manager]) -nisNetgroup = ObjectClass('1.3.6.1.1.1.2.8', name='nisNetgroup', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Abstraction of a netgroup. May refer to other netgroups', must=[attribute_types.cn], may=[attribute_types.nisNetgroupTriple, attribute_types.memberNisNetgroup, attribute_types.description]) -nisMap = ObjectClass('1.3.6.1.1.1.2.9', name='nisMap', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='A generic abstraction of a NIS map', must=[attribute_types.nisMapName], may=[attribute_types.description]) -nisObject = ObjectClass('1.3.6.1.1.1.2.10', name='nisObject', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='An entry in a NIS map', must=[attribute_types.cn, attribute_types.nisMapEntry, attribute_types.nisMapName]) -ieee802Device = ObjectClass('1.3.6.1.1.1.2.11', name='ieee802Device', sup=top, kind=ObjectClassKind.AUXILIARY, desc='A device with a MAC address; device SHOULD be used as a structural class', may=[attribute_types.macAddress]) -bootableDevice = ObjectClass('1.3.6.1.1.1.2.12', name='bootableDevice', sup=top, kind=ObjectClassKind.AUXILIARY, desc='A device with boot parameters; device SHOULD be used as a structural class', may=[attribute_types.bootFile, attribute_types.bootParameter]) -nisKeyObject = ObjectClass('1.3.6.1.1.1.2.14', name='nisKeyObject', sup=top, kind=ObjectClassKind.AUXILIARY, desc='An object with a public and secret key', must=[attribute_types.cn, attribute_types.nisPublicKey, attribute_types.nisSecretKey], may=[attribute_types.uidNumber, attribute_types.description]) -nisDomainObject = ObjectClass('1.3.6.1.1.1.2.15', name='nisDomainObject', sup=top, kind=ObjectClassKind.AUXILIARY, desc='Associates a NIS domain with a naming context', must=[attribute_types.nisDomain]) -automountMap = ObjectClass('1.3.6.1.1.1.2.16', name='automountMap', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.automountMapName], may=[attribute_types.description]) -automount = ObjectClass('1.3.6.1.1.1.2.17', name='automount', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='Automount information', must=[attribute_types.automountKey, attribute_types.automountInformation], may=[attribute_types.description]) -groupOfMembers = ObjectClass('1.3.6.1.1.1.2.18', name='groupOfMembers', sup=top, kind=ObjectClassKind.STRUCTURAL, desc='A group with members (DNs)', must=[attribute_types.cn], may=[attribute_types.businessCategory, attribute_types.seeAlso, attribute_types.owner, attribute_types.ou, attribute_types.o, attribute_types.description, attribute_types.member]) - -ALL = ALL + RFC3112_ALL + ( - posixAccount, - shadowAccount, - posixGroup, - ipService, - ipProtocol, - oncRpc, - ipHost, - ipNetwork, - nisNetgroup, - nisMap, - nisObject, - ieee802Device, - bootableDevice, - nisKeyObject, - nisDomainObject, - automountMap, - automount, - groupOfMembers, -) diff --git a/ldapserver/schema/rfc2307bis/syntaxes.py b/ldapserver/schema/rfc2307bis/syntaxes.py deleted file mode 100644 index 8156f9b078969b2b4651bd7cdb2f730dd22be95f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2307bis/syntaxes.py +++ /dev/null @@ -1,7 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4524.syntaxes import * -from ..rfc3112.syntaxes import ALL as RFC3112_ALL - -ALL = ALL + RFC3112_ALL diff --git a/ldapserver/schema/rfc2798/__init__.py b/ldapserver/schema/rfc2798/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2798/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc2798/attribute_types.py b/ldapserver/schema/rfc2798/attribute_types.py deleted file mode 100644 index 67ecd33392fe9468cc8724935ab5c6310ce4ca49..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2798/attribute_types.py +++ /dev/null @@ -1,37 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import,unused-import - -from ..types import AttributeType -# RFC2798 is originally based on the old LDAPv3 RFC2256, the old -# COSINE RFC1274 and RFC2079 (for labeledURI). RFC2256 and RFC1274 -# were obsoleted by RFC4524 and RFC4519. They also updated RFC2798. -from ..rfc4524.attribute_types import * -from ..rfc2079.attribute_types import labeledURI, ALL as RFC2079_ALL -from ..rfc4523.attribute_types import userCertificate -from ..rfc1274.attribute_types import audio, photo -from . import syntaxes, matching_rules - -carLicense = AttributeType('2.16.840.1.113730.3.1.1', name='carLicense', desc='vehicle license or registration plate', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -departmentNumber = AttributeType('2.16.840.1.113730.3.1.2', name='departmentNumber', desc='identifies a department within an organization', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -displayName = AttributeType('2.16.840.1.113730.3.1.241', name='displayName', desc='preferred name of a person to be used when displaying entries', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(), single_value=True) -employeeNumber = AttributeType('2.16.840.1.113730.3.1.3', name='employeeNumber', desc='numerically identifies an employee within an organization', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(), single_value=True) -employeeType = AttributeType('2.16.840.1.113730.3.1.4', name='employeeType', desc='type of employment for a person', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -jpegPhoto = AttributeType('0.9.2342.19200300.100.1.60', name='jpegPhoto', desc='a JPEG image', syntax=syntaxes.JPEG()) -preferredLanguage = AttributeType('2.16.840.1.113730.3.1.39', name='preferredLanguage', desc='preferred written or spoken language for a person', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(), single_value=True) -userSMIMECertificate = AttributeType('2.16.840.1.113730.3.1.40', name='userSMIMECertificate', desc='PKCS#7 SignedData used to support S/MIME', syntax=syntaxes.Binary()) -userPKCS12 = AttributeType('2.16.840.1.113730.3.1.216', name='userPKCS12', desc='PKCS #12 PFX PDU for exchange of personal identity information', syntax=syntaxes.Binary()) - -ALL = ALL + RFC2079_ALL + ( - userCertificate, # RFC4523 - audio, # RFC1274 - photo, # RFC1274 - carLicense, - departmentNumber, - displayName, - employeeNumber, - employeeType, - jpegPhoto, - preferredLanguage, - userSMIMECertificate, - userPKCS12, -) diff --git a/ldapserver/schema/rfc2798/matching_rules.py b/ldapserver/schema/rfc2798/matching_rules.py deleted file mode 100644 index cea092d5209d391c17d48ec1cf36a8e32aa8bf09..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2798/matching_rules.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4524.matching_rules import * diff --git a/ldapserver/schema/rfc2798/object_classes.py b/ldapserver/schema/rfc2798/object_classes.py deleted file mode 100644 index 370759b973e9720f3138f7ce3dee653411d7d879..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2798/object_classes.py +++ /dev/null @@ -1,12 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import ObjectClass, ObjectClassKind -from ..rfc4524.object_classes import * -from . import attribute_types - -inetOrgPerson = ObjectClass('2.16.840.1.113730.3.2.2', name='inetOrgPerson', sup=organizationalPerson, kind=ObjectClassKind.STRUCTURAL, may=[attribute_types.businessCategory, attribute_types.carLicense, attribute_types.departmentNumber, attribute_types.displayName, attribute_types.employeeNumber, attribute_types.employeeType, attribute_types.givenName, attribute_types.homePhone, attribute_types.homePostalAddress, attribute_types.initials, attribute_types.jpegPhoto, attribute_types.labeledURI, attribute_types.mail, attribute_types.manager, attribute_types.mobile, attribute_types.o, attribute_types.pager, attribute_types.roomNumber, attribute_types.secretary, attribute_types.uid, attribute_types.x500UniqueIdentifier, attribute_types.preferredLanguage, attribute_types.userSMIMECertificate, attribute_types.userPKCS12, attribute_types.userCertificate, attribute_types.audio, attribute_types.photo]) - -ALL = ALL + ( - inetOrgPerson, -) diff --git a/ldapserver/schema/rfc2798/syntaxes.py b/ldapserver/schema/rfc2798/syntaxes.py deleted file mode 100644 index ee6a435aef6727cf010626c2b00d1afa7f1d02f1..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc2798/syntaxes.py +++ /dev/null @@ -1,7 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4524.syntaxes import * -from ..rfc2252.syntaxes import Binary - -ALL = ALL + (Binary,) diff --git a/ldapserver/schema/rfc3112/__init__.py b/ldapserver/schema/rfc3112/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc3112/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc3112/attribute_types.py b/ldapserver/schema/rfc3112/attribute_types.py deleted file mode 100644 index 534b574c2ef72089ca04bb4f1929dfa30083b49c..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc3112/attribute_types.py +++ /dev/null @@ -1,10 +0,0 @@ -from ..types import AttributeType, AttributeTypeUsage -from . import syntaxes, matching_rules - -supportedAuthPasswordSchemes = AttributeType('1.3.6.1.4.1.4203.1.3.3', name='supportedAuthPasswordSchemes', desc='supported password storage schemes', equality=matching_rules.caseIgnoreIA5Match, syntax=syntaxes.IA5String(32), usage=AttributeTypeUsage.dSAOperation) -authPassword = AttributeType('1.3.6.1.4.1.4203.1.3.4', name='authPassword', desc='password authentication information', equality=matching_rules.authPasswordExactMatch, syntax=syntaxes.AuthPasswordSyntax()) - -ALL = ( - supportedAuthPasswordSchemes, - authPassword, -) diff --git a/ldapserver/schema/rfc3112/matching_rules.py b/ldapserver/schema/rfc3112/matching_rules.py deleted file mode 100644 index d0aee5f6dfaf2ccca5134816b898f103537cdaba..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc3112/matching_rules.py +++ /dev/null @@ -1,16 +0,0 @@ - -# pylint: disable=unused-import - -from ..types import MatchingRule -from ..rfc4517.matching_rules import caseIgnoreIA5Match -from . import syntaxes - -authPasswordExactMatch = MatchingRule('1.3.6.1.4.1.4203.1.2.2', name='authPasswordExactMatch', desc='authentication password exact matching rule', syntax=syntaxes.AuthPasswordSyntax()) - -# We won't implement any actual schemes here, so the default behaviour of MatchingRule (return UNDEFINED) is fine. -authPasswordMatch = MatchingRule('1.3.6.1.4.1.4203.1.2.3', name='authPasswordMatch', desc='authentication password matching rule', syntax=syntaxes.OctetString(128)) - -ALL = ( - authPasswordExactMatch, - authPasswordMatch, -) diff --git a/ldapserver/schema/rfc3112/object_classes.py b/ldapserver/schema/rfc3112/object_classes.py deleted file mode 100644 index 749d91ff0c3a101f5d4f3909b6cfa0dd1ba08910..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc3112/object_classes.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..types import ObjectClass, ObjectClassKind -from . import attribute_types - -authPasswordObject = ObjectClass('1.3.6.1.4.1.4203.1.4.7', name='authPasswordObject', desc='authentication password mix in class', kind=ObjectClassKind.AUXILIARY, may=[attribute_types.authPassword]) - -ALL = ( - authPasswordObject, -) diff --git a/ldapserver/schema/rfc3112/syntaxes.py b/ldapserver/schema/rfc3112/syntaxes.py deleted file mode 100644 index c954aef0b57f8c7ca904a573c734fa6cd8620010..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc3112/syntaxes.py +++ /dev/null @@ -1,12 +0,0 @@ - -# pylint: disable=unused-import - -from ..rfc4517.syntaxes import IA5String, OctetString, BytesSyntax - -class AuthPasswordSyntax(BytesSyntax): - oid = '1.3.6.1.4.1.4203.1.1.2' - desc = 'authentication password syntax' - -ALL = ( - AuthPasswordSyntax, -) diff --git a/ldapserver/schema/rfc4512/__init__.py b/ldapserver/schema/rfc4512/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4512/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc4512/attribute_types.py b/ldapserver/schema/rfc4512/attribute_types.py deleted file mode 100644 index dc1f34005b098a9e29f144d195b5f05dca926360..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4512/attribute_types.py +++ /dev/null @@ -1,54 +0,0 @@ -from ..types import AttributeType, AttributeTypeUsage -from . import syntaxes, matching_rules - -aliasedObjectName = AttributeType('2.5.4.1', name='aliasedObjectName', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN(), single_value=True) -objectClass = AttributeType('2.5.4.0', name='objectClass', equality=matching_rules.objectIdentifierMatch, syntax=syntaxes.OID()) -creatorsName = AttributeType('2.5.18.3', name='creatorsName', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -createTimestamp = AttributeType('2.5.18.1', name='createTimestamp', equality=matching_rules.generalizedTimeMatch, ordering=matching_rules.generalizedTimeOrderingMatch, syntax=syntaxes.GeneralizedTime(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -modifiersName = AttributeType('2.5.18.4', name='modifiersName', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -modifyTimestamp = AttributeType('2.5.18.2', name='modifyTimestamp', equality=matching_rules.generalizedTimeMatch, ordering=matching_rules.generalizedTimeOrderingMatch, syntax=syntaxes.GeneralizedTime(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -structuralObjectClass = AttributeType('2.5.21.9', name='structuralObjectClass', equality=matching_rules.objectIdentifierMatch, syntax=syntaxes.OID(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -governingStructureRule = AttributeType('2.5.21.10', name='governingStructureRule', equality=matching_rules.integerMatch, syntax=syntaxes.INTEGER(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -subschemaSubentry = AttributeType('2.5.18.10', name='subschemaSubentry', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN(), single_value=True, no_user_modification=True, usage=AttributeTypeUsage.directoryOperation) -objectClasses = AttributeType('2.5.21.6', name='objectClasses', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.ObjectClassDescription(), usage=AttributeTypeUsage.directoryOperation) -attributeTypes = AttributeType('2.5.21.5', name='attributeTypes', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.AttributeTypeDescription(), usage=AttributeTypeUsage.directoryOperation) -matchingRules = AttributeType('2.5.21.4', name='matchingRules', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.MatchingRuleDescription(), usage=AttributeTypeUsage.directoryOperation) -matchingRuleUse = AttributeType('2.5.21.8', name='matchingRuleUse', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.MatchingRuleUseDescription(), usage=AttributeTypeUsage.directoryOperation) -ldapSyntaxes = AttributeType('1.3.6.1.4.1.1466.101.120.16', name='ldapSyntaxes', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.LDAPSyntaxDescription(), usage=AttributeTypeUsage.directoryOperation) -dITContentRules = AttributeType('2.5.21.2', name='dITContentRules', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.DITContentRuleDescription(), usage=AttributeTypeUsage.directoryOperation) -dITStructureRules = AttributeType('2.5.21.1', name='dITStructureRules', equality=matching_rules.integerFirstComponentMatch, syntax=syntaxes.DITStructureRuleDescription(), usage=AttributeTypeUsage.directoryOperation) -nameForms = AttributeType('2.5.21.7', name='nameForms', equality=matching_rules.objectIdentifierFirstComponentMatch, syntax=syntaxes.NameFormDescription(), usage=AttributeTypeUsage.directoryOperation) -altServer = AttributeType('1.3.6.1.4.1.1466.101.120.6', name='altServer', syntax=syntaxes.IA5String(), usage=AttributeTypeUsage.dSAOperation) -namingContexts = AttributeType('1.3.6.1.4.1.1466.101.120.5', name='namingContexts', syntax=syntaxes.DN(), usage=AttributeTypeUsage.dSAOperation) -supportedControl = AttributeType('1.3.6.1.4.1.1466.101.120.13', name='supportedControl', syntax=syntaxes.OID(), usage=AttributeTypeUsage.dSAOperation) -supportedExtension = AttributeType('1.3.6.1.4.1.1466.101.120.7', name='supportedExtension', syntax=syntaxes.OID(), usage=AttributeTypeUsage.dSAOperation) -supportedFeatures = AttributeType('1.3.6.1.4.1.4203.1.3.5', name='supportedFeatures', equality=matching_rules.objectIdentifierMatch, syntax=syntaxes.OID(), usage=AttributeTypeUsage.dSAOperation) -supportedLDAPVersion = AttributeType('1.3.6.1.4.1.1466.101.120.15', name='supportedLDAPVersion', syntax=syntaxes.INTEGER(), usage=AttributeTypeUsage.dSAOperation) -supportedSASLMechanisms = AttributeType('1.3.6.1.4.1.1466.101.120.14', name='supportedSASLMechanisms', syntax=syntaxes.DirectoryString(), usage=AttributeTypeUsage.dSAOperation) - -ALL = ( - aliasedObjectName, - objectClass, - creatorsName, - createTimestamp, - modifiersName, - modifyTimestamp, - structuralObjectClass, - governingStructureRule, - subschemaSubentry, - objectClasses, - attributeTypes, - matchingRules, - matchingRuleUse, - ldapSyntaxes, - dITContentRules, - dITStructureRules, - nameForms, - altServer, - namingContexts, - supportedControl, - supportedExtension, - supportedFeatures, - supportedLDAPVersion, - supportedSASLMechanisms, -) diff --git a/ldapserver/schema/rfc4512/matching_rules.py b/ldapserver/schema/rfc4512/matching_rules.py deleted file mode 100644 index ee6bd4a191364fabd69bfe9ea118d6d6fe2eec1d..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4512/matching_rules.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4517.matching_rules import * diff --git a/ldapserver/schema/rfc4512/object_classes.py b/ldapserver/schema/rfc4512/object_classes.py deleted file mode 100644 index 8f8f9d35457bcec909db0919d4c9c83481d5eaf9..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4512/object_classes.py +++ /dev/null @@ -1,14 +0,0 @@ -from ..types import ObjectClass, ObjectClassKind -from . import attribute_types - -top = ObjectClass('2.5.6.0', 'top', kind=ObjectClassKind.ABSTRACT, must=[attribute_types.objectClass]) -alias = ObjectClass('2.5.6.1', 'alias', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.aliasedObjectName]) -subschema = ObjectClass('2.5.20.1', 'subschema', kind=ObjectClassKind.AUXILIARY, may=[attribute_types.dITStructureRules, attribute_types.nameForms, attribute_types.dITContentRules, attribute_types.objectClasses, attribute_types.attributeTypes, attribute_types.matchingRules, attribute_types.matchingRuleUse]) -extensibleObject = ObjectClass('1.3.6.1.4.1.1466.101.120.111', 'extensibleObject', sup=top, kind=ObjectClassKind.AUXILIARY) - -ALL = ( - top, - alias, - subschema, - extensibleObject, -) diff --git a/ldapserver/schema/rfc4512/syntaxes.py b/ldapserver/schema/rfc4512/syntaxes.py deleted file mode 100644 index 657f66ef37f6ad42639e97196fcefc5fa041e52b..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4512/syntaxes.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4517.syntaxes import * diff --git a/ldapserver/schema/rfc4517/__init__.py b/ldapserver/schema/rfc4517/__init__.py deleted file mode 100644 index 051bbed218cb085261e986c436a385eede5eb342..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4517/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules diff --git a/ldapserver/schema/rfc4517/matching_rules.py b/ldapserver/schema/rfc4517/matching_rules.py deleted file mode 100644 index ea211434fcf32f889b97ed9c3261ffce6c3314f3..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4517/matching_rules.py +++ /dev/null @@ -1,170 +0,0 @@ -from ..types import MatchingRule -from ... import rfc4518_stringprep -from . import syntaxes - -class GenericMatchingRule(MatchingRule): - def match_equal(self, schema, attribute_value, assertion_value): - return attribute_value == assertion_value - - def match_less(self, schema, attribute_value, assertion_value): - return attribute_value < assertion_value - - def match_greater_or_equal(self, schema, attribute_value, assertion_value): - return attribute_value >= assertion_value - -class StringMatchingRule(MatchingRule): - def __init__(self, oid, name, syntax, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING): - super().__init__(oid, name, syntax) - self.matching_type = matching_type - - def match_equal(self, schema, attribute_value, assertion_value): - try: - attribute_value = rfc4518_stringprep.prepare(attribute_value, self.matching_type) - assertion_value = rfc4518_stringprep.prepare(assertion_value, self.matching_type) - except ValueError: - return None - return attribute_value == assertion_value - - def match_less(self, schema, attribute_value, assertion_value): - try: - attribute_value = rfc4518_stringprep.prepare(attribute_value, self.matching_type) - assertion_value = rfc4518_stringprep.prepare(assertion_value, self.matching_type) - except ValueError: - return None - return attribute_value < assertion_value - - def match_greater_or_equal(self, schema, attribute_value, assertion_value): - try: - attribute_value = rfc4518_stringprep.prepare(attribute_value, self.matching_type) - assertion_value = rfc4518_stringprep.prepare(assertion_value, self.matching_type) - except ValueError: - return None - return attribute_value >= assertion_value - - def match_substr(self, schema, attribute_value, inital_substring, any_substrings, final_substring): - try: - attribute_value = rfc4518_stringprep.prepare(attribute_value, self.matching_type) - if inital_substring: - inital_substring = rfc4518_stringprep.prepare(inital_substring, self.matching_type, rfc4518_stringprep.SubstringType.INITIAL) - any_substrings = [rfc4518_stringprep.prepare(substring, self.matching_type, rfc4518_stringprep.SubstringType.ANY) for substring in any_substrings] - if final_substring: - final_substring = rfc4518_stringprep.prepare(final_substring, self.matching_type, rfc4518_stringprep.SubstringType.FINAL) - except ValueError: - return None - if inital_substring: - if not attribute_value.startswith(inital_substring): - return False - attribute_value = attribute_value[len(inital_substring):] - if final_substring: - if not attribute_value.endswith(final_substring): - return False - attribute_value = attribute_value[:-len(final_substring)] - for substring in any_substrings: - index = attribute_value.find(substring) - if index == -1: - return False - attribute_value = attribute_value[index+len(substring):] - return True - -class StringListMatchingRule(MatchingRule): - def __init__(self, oid, name, syntax, matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING): - super().__init__(oid, name, syntax) - self.matching_type = matching_type - - # Values are both lists of str - def match_equal(self, schema, attribute_value, assertion_value): - try: - attribute_value = [rfc4518_stringprep.prepare(line, self.matching_type) for line in attribute_value] - assertion_value = [rfc4518_stringprep.prepare(line, self.matching_type) for line in assertion_value] - except ValueError: - return None - return attribute_value == assertion_value - -class FirstComponentMatchingRule(MatchingRule): - def __init__(self, oid, name, syntax, attribute_name, matching_rule): - super().__init__(oid, name, syntax) - self.attribute_name = attribute_name - self.matching_rule = matching_rule - - def match_equal(self, schema, attribute_value, assertion_value): - if not hasattr(attribute_value, self.attribute_name): - return None - return self.matching_rule.match_equal(schema, getattr(attribute_value, self.attribute_name)(), assertion_value) - -class OIDMatchingRule(MatchingRule): - def match_equal(self, schema, attribute_value, assertion_value): - attribute_value = schema.get_numeric_oid(attribute_value) - assertion_value = schema.get_numeric_oid(assertion_value) - if assertion_value is None: - return None - return attribute_value == assertion_value - -bitStringMatch = GenericMatchingRule('2.5.13.16', name='bitStringMatch', syntax=syntaxes.BitString()) -booleanMatch = GenericMatchingRule('2.5.13.13', name='booleanMatch', syntax=syntaxes.Boolean()) -caseExactIA5Match = StringMatchingRule('1.3.6.1.4.1.1466.109.114.1', name='caseExactIA5Match', syntax=syntaxes.IA5String(), matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING) -caseExactMatch = StringMatchingRule('2.5.13.5', name='caseExactMatch', syntax=syntaxes.DirectoryString(), matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING) -caseExactOrderingMatch = StringMatchingRule('2.5.13.6', name='caseExactOrderingMatch', syntax=syntaxes.DirectoryString(), matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING) -caseExactSubstringsMatch = StringMatchingRule('2.5.13.7', name='caseExactSubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.EXACT_STRING) -caseIgnoreIA5Match = StringMatchingRule('1.3.6.1.4.1.1466.109.114.2', name='caseIgnoreIA5Match', syntax=syntaxes.IA5String(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreIA5SubstringsMatch = StringMatchingRule('1.3.6.1.4.1.1466.109.114.3', name='caseIgnoreIA5SubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreListMatch = StringListMatchingRule('2.5.13.11', name='caseIgnoreListMatch', syntax=syntaxes.PostalAddress(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreListSubstringsMatch = StringListMatchingRule('2.5.13.12', name='caseIgnoreListSubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreMatch = StringMatchingRule('2.5.13.2', name='caseIgnoreMatch', syntax=syntaxes.DirectoryString(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreOrderingMatch = StringMatchingRule('2.5.13.3', name='caseIgnoreOrderingMatch', syntax=syntaxes.DirectoryString(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -caseIgnoreSubstringsMatch = StringMatchingRule('2.5.13.4', name='caseIgnoreSubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING) -directoryStringFirstComponentMatch = FirstComponentMatchingRule('2.5.13.31', name='directoryStringFirstComponentMatch', syntax=syntaxes.DirectoryString(), attribute_name='get_first_component_string', matching_rule=caseIgnoreMatch) -distinguishedNameMatch = GenericMatchingRule('2.5.13.1', name='distinguishedNameMatch', syntax=syntaxes.DN()) -generalizedTimeMatch = GenericMatchingRule('2.5.13.27', name='generalizedTimeMatch', syntax=syntaxes.GeneralizedTime()) -generalizedTimeOrderingMatch = GenericMatchingRule('2.5.13.28', name='generalizedTimeOrderingMatch', syntax=syntaxes.GeneralizedTime()) -integerMatch = GenericMatchingRule('2.5.13.14', name='integerMatch', syntax=syntaxes.INTEGER()) -integerFirstComponentMatch = FirstComponentMatchingRule('2.5.13.29', name='integerFirstComponentMatch', syntax=syntaxes.INTEGER(), attribute_name='get_first_component_integer', matching_rule=integerMatch) -integerOrderingMatch = GenericMatchingRule('2.5.13.15', name='integerOrderingMatch', syntax=syntaxes.INTEGER()) -# Optional and implementation-specific, we simply never match -keywordMatch = MatchingRule('2.5.13.33', name='keywordMatch', syntax=syntaxes.DirectoryString()) -numericStringMatch = StringMatchingRule('2.5.13.8', name='numericStringMatch', syntax=syntaxes.NumericString(), matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING) -numericStringOrderingMatch = StringMatchingRule('2.5.13.9', name='numericStringOrderingMatch', syntax=syntaxes.NumericString(), matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING) -numericStringSubstringsMatch = StringMatchingRule('2.5.13.10', name='numericStringSubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.NUMERIC_STRING) -objectIdentifierMatch = OIDMatchingRule('2.5.13.0', name='objectIdentifierMatch', syntax=syntaxes.OID()) -objectIdentifierFirstComponentMatch = FirstComponentMatchingRule('2.5.13.30', name='objectIdentifierFirstComponentMatch', syntax=syntaxes.OID(), attribute_name='get_first_component_oid', matching_rule=objectIdentifierMatch) -octetStringMatch = GenericMatchingRule('2.5.13.17', name='octetStringMatch', syntax=syntaxes.OctetString()) -octetStringOrderingMatch = GenericMatchingRule('2.5.13.18', name='octetStringOrderingMatch', syntax=syntaxes.OctetString()) -telephoneNumberMatch = StringMatchingRule('2.5.13.20', name='telephoneNumberMatch', syntax=syntaxes.TelephoneNumber(), matching_type=rfc4518_stringprep.MatchingType.TELEPHONE_NUMBER) -telephoneNumberSubstringsMatch = StringMatchingRule('2.5.13.21', name='telephoneNumberSubstringsMatch', syntax=syntaxes.SubstringAssertion(), matching_type=rfc4518_stringprep.MatchingType.TELEPHONE_NUMBER) -uniqueMemberMatch = GenericMatchingRule('2.5.13.23', name='uniqueMemberMatch', syntax=syntaxes.NameAndOptionalUID()) -# Optional and implementation-specific, we simply never match -wordMatch = MatchingRule('2.5.13.32', name='wordMatch', syntax=syntaxes.DirectoryString()) - -ALL = ( - bitStringMatch, - booleanMatch, - caseExactIA5Match, - caseExactMatch, - caseExactOrderingMatch, - caseExactSubstringsMatch, - caseIgnoreIA5Match, - caseIgnoreIA5SubstringsMatch, - caseIgnoreListMatch, - caseIgnoreListSubstringsMatch, - caseIgnoreMatch, - caseIgnoreOrderingMatch, - caseIgnoreSubstringsMatch, - directoryStringFirstComponentMatch, - distinguishedNameMatch, - generalizedTimeMatch, - generalizedTimeOrderingMatch, - integerFirstComponentMatch, - integerMatch, - integerOrderingMatch, - #keywordMatch, - numericStringMatch, - numericStringOrderingMatch, - numericStringSubstringsMatch, - objectIdentifierFirstComponentMatch, - objectIdentifierMatch, - octetStringMatch, - octetStringOrderingMatch, - telephoneNumberMatch, - telephoneNumberSubstringsMatch, - uniqueMemberMatch, - #wordMatch, -) diff --git a/ldapserver/schema/rfc4517/syntaxes.py b/ldapserver/schema/rfc4517/syntaxes.py deleted file mode 100644 index ea02285b86ef8f8ee6d3d617ca253666faaeb54e..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4517/syntaxes.py +++ /dev/null @@ -1,409 +0,0 @@ -import re -import datetime - -from ..types import Syntax -from ... import dn - -# Base classes -class StringSyntax(Syntax): - @staticmethod - def encode(schema, value): - return value.encode('utf8') - - @staticmethod - def decode(schema, raw_value): - return raw_value.decode('utf8') - -class BytesSyntax(Syntax): - @staticmethod - def encode(schema, value): - return value - - @staticmethod - def decode(schema, raw_value): - return raw_value - -class SchemaElementSyntax(Syntax): - @staticmethod - def encode(schema, value): - return value.to_definition().encode('utf8') - - @staticmethod - def decode(schema, raw_value): - return None - -# Syntax definitions -class AttributeTypeDescription(SchemaElementSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.3' - desc = 'Attribute Type Description' - -class BitString(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.6' - desc = 'Bit String' - -class Boolean(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.7' - desc = 'Boolean' - - @staticmethod - def encode(schema, value): - return b'TRUE' if value else b'FALSE' - - @staticmethod - def decode(schema, raw_value): - if raw_value == b'TRUE': - return True - elif raw_value == b'FALSE': - return False - else: - return None - -class CountryString(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.11' - desc = 'Country String' - -class DeliveryMethod(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.14' - desc = 'Delivery Method' - -class DirectoryString(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.15' - desc = 'Directory String' - -class DITContentRuleDescription(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.16' - desc = 'DIT Content Rule Description' - -class DITStructureRuleDescription(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.17' - desc = 'DIT Structure Rule Description' - -class DN(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.12' - desc = 'DN' - - @staticmethod - def encode(schema, value): - return str(value).encode('utf8') - - @staticmethod - def decode(schema, raw_value): - try: - return dn.DN.from_str(raw_value.decode('utf8')) - except (UnicodeDecodeError, TypeError, ValueError): - return None - -class EnhancedGuide(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.21' - desc = 'Enhanced Guide' - -class FacsimileTelephoneNumber(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.22' - desc = 'Facsimile Telephone Number' - -class Fax(BytesSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.23' - desc = 'Fax' - -class GeneralizedTime(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.24' - desc = 'Generalized Time' - - @staticmethod - def encode(schema, value): - str_value = value.strftime('%Y%m%d%H%M%S.%f') - if value.tzinfo == datetime.timezone.utc: - str_value += 'Z' - elif value.tzinfo is not None: - delta_seconds = value.tzinfo.utcoffset(value).total_seconds() - if delta_seconds < 0: - str_value += '-' - delta_seconds = -delta_seconds - else: - str_value += '+' - hour = delta_seconds // 3600 - minute = (delta_seconds % 3600) // 60 - str_value += '%02d%02d'%(hour, minute) - return str_value.encode('ascii') - - @staticmethod - def decode(schema, raw_value): - try: - raw_value = raw_value.decode('utf8') - except UnicodeDecodeError: - return None - match = re.fullmatch(r'([0-9]{10})(|[0-9]{2}|[0-9]{4})(|[,.][0-9]+)(Z|[+-][0-9]{2}|[+-][0-9]{4})', raw_value) - if match is None: - return None - main, minute_second, fraction, timezone = match.groups() - fraction = float('0.' + (fraction[1:] or '0')) - result = datetime.datetime.strptime(main, '%Y%m%d%H') - if not minute_second: - result += datetime.timedelta(hours=fraction) - if len(minute_second) == 2: - result += datetime.timedelta(minutes=int(minute_second)+fraction) - elif len(minute_second) == 4: - minute = minute_second[:2] - second = minute_second[2:4] - result += datetime.timedelta(minutes=int(minute), seconds=int(second)+fraction) - if timezone == 'Z': - result = result.replace(tzinfo=datetime.timezone.utc) - elif timezone: - sign, hour, minute = timezone[0], timezone[1:3], (timezone[3:5] or '00') - delta = datetime.timedelta(hours=int(hour), minutes=int(minute)) - if sign == '+': - result = result.replace(tzinfo=datetime.timezone(delta)) - else: - result = result.replace(tzinfo=datetime.timezone(-delta)) - return result - -class Guide(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.25' - desc = 'Guide' - -class IA5String(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.26' - desc = 'IA5 String' - -class INTEGER(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.27' - desc = 'INTEGER' - - @staticmethod - def encode(schema, value): - return str(value).encode('utf8') - - @staticmethod - def decode(schema, raw_value): - if not raw_value or not raw_value.split(b'-', 1)[-1].isdigit(): - return None - return int(raw_value.decode('utf8')) - -class JPEG(BytesSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.28' - desc = 'JPEG' - -class LDAPSyntaxDescription(SchemaElementSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.54' - desc = 'LDAP Syntax Description' - -class MatchingRuleDescription(SchemaElementSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.30' - desc = 'Matching Rule Description' - -class MatchingRuleUseDescription(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.31' - desc = 'Matching Rule Use Description' - -class NameAndOptionalUID(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.34' - desc = 'Name And Optional UID' - - @staticmethod - def encode(schema, value): - return DN.encode(schema, value) - - @staticmethod - def decode(schema, raw_value): - escaped = False - dn_part = raw_value - bitstr_part = b'' # pylint: disable=unused-variable - for index, byte in enumerate(raw_value): - byte = bytes((byte,)) - if escaped: - escaped = False - elif byte == b'\\': - escaped = True - elif byte == b'#': - dn_part = raw_value[:index] - bitstr_part = raw_value[index+1:] - break - # We need to find a good representation of this type, maybe a subclass - # of dn.DN that carries the bitstring part as an attribute. - #if bitstr_part: - # return DN.decode(dn_part), BitString.decode(bitstr_part) - return DN.decode(schema, dn_part) - -class NameFormDescription(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.35' - desc = 'Name Form Description' - -class NumericString(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.36' - desc = 'Numeric String' - -class ObjectClassDescription(SchemaElementSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.37' - desc = 'Object Class Description' - -class OctetString(BytesSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.40' - desc = 'Octet String' - -class OID(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.38' - desc = 'OID' - -class OtherMailbox(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.39' - desc = 'Other Mailbox' - -class PostalAddress(Syntax): - # 3.3.28. Postal Address - # - # A value of the Postal Address syntax is a sequence of strings of one - # or more arbitrary UCS characters, which form an address in a physical - # mail system. - # - # The LDAP-specific encoding of a value of this syntax is defined by - # the following ABNF: - # - # - # PostalAddress = line *( DOLLAR line ) - # line = 1*line-char - # line-char = %x00-23 - # / (%x5C "24") ; escaped "$" - # / %x25-5B - # / (%x5C "5C") ; escaped "\" - # / %x5D-7F - # / UTFMB - # - # Each character string (i.e., <line>) of a postal address value is - # encoded as a UTF-8 [RFC3629] string, except that "\" and "$" - # characters, if they occur in the string, are escaped by a "\" - # character followed by the two hexadecimal digit code for the - # character. The <DOLLAR> and <UTFMB> rules are defined in [RFC4512]. - # - # Many servers limit the postal address to no more than six lines of no - # more than thirty characters each. - # - # Example: - # 1234 Main St.$Anytown, CA 12345$USA - # \241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA - # - # The LDAP definition for the Postal Address syntax is: - # - # ( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' ) - # - # This syntax corresponds to the PostalAddress ASN.1 type from [X.520]; - # that is - # - # PostalAddress ::= SEQUENCE SIZE(1..ub-postal-line) OF - # DirectoryString { ub-postal-string } - # - # The values of ub-postal-line and ub-postal-string (both integers) are - # implementation defined. Non-normative definitions appear in [X.520]. - - oid = '1.3.6.1.4.1.1466.115.121.1.41' - desc = 'Postal Address' - - # Native values are lists of str - @staticmethod - def encode(schema, value): - return '$'.join([line.replace('\\', '\\5C').replace('$', '\\24') for line in value]).encode('utf8') - - @staticmethod - def decode(schema, raw_value): - return [line.replace('\\24', '$').replace('\\5C', '\\') for line in raw_value.decode('utf8').split('$')] - -class PrintableString(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.44' - desc = 'Printable String' - -class SubstringAssertion(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.58' - desc = 'Substring Assertion' - -class TelephoneNumber(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.50' - desc = 'Telephone Number' - -class TeletexTerminalIdentifier(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.51' - desc = 'Teletex Terminal Identifier' - -class TelexNumber(StringSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.52' - desc = 'Telex Number' - -class UTCTime(Syntax): - oid = '1.3.6.1.4.1.1466.115.121.1.53' - desc = 'UTC Time' - - @staticmethod - def encode(schema, value): - str_value = value.strftime('%y%m%d%H%M%S') - if value.tzinfo == datetime.timezone.utc: - str_value += 'Z' - elif value.tzinfo is not None: - delta_seconds = value.tzinfo.utcoffset(value).total_seconds() - if delta_seconds < 0: - str_value += '-' - delta_seconds = -delta_seconds - else: - str_value += '+' - hour = delta_seconds // 3600 - minute = (delta_seconds % 3600) // 60 - str_value += '%02d%02d'%(hour, minute) - return str_value.encode('ascii') - - @staticmethod - def decode(schema, raw_value): - try: - raw_value = raw_value.decode('utf8') - except UnicodeDecodeError: - return None - match = re.fullmatch(r'([0-9]{10})(|[0-9]{2})(|Z|[+-][0-9]{4})', raw_value) - if match is None: - return None - main, seconds, timezone = match.groups() - result = datetime.datetime.strptime(main, '%y%m%d%H%M') - if seconds: - result = result.replace(second=int(seconds)) - if timezone == 'Z': - result = result.replace(tzinfo=datetime.timezone.utc) - elif timezone: - sign, hour, minute = timezone[0], timezone[1:3], timezone[3:5] - delta = datetime.timedelta(hours=int(hour), minutes=int(minute)) - if sign == '+': - result = result.replace(tzinfo=datetime.timezone(delta)) - else: - result = result.replace(tzinfo=datetime.timezone(-delta)) - return result - -ALL = ( - AttributeTypeDescription, - BitString, - Boolean, - CountryString, - DeliveryMethod, - DirectoryString, - DITContentRuleDescription, - DITStructureRuleDescription, - DN, - EnhancedGuide, - FacsimileTelephoneNumber, - Fax, - GeneralizedTime, - Guide, - IA5String, - INTEGER, - JPEG, - LDAPSyntaxDescription, - MatchingRuleDescription, - MatchingRuleUseDescription, - NameAndOptionalUID, - NameFormDescription, - NumericString, - ObjectClassDescription, - OctetString, - OID, - OtherMailbox, - PostalAddress, - PrintableString, - SubstringAssertion, - TelephoneNumber, - TeletexTerminalIdentifier, - TelexNumber, - UTCTime, -) diff --git a/ldapserver/schema/rfc4519/__init__.py b/ldapserver/schema/rfc4519/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4519/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc4519/attribute_types.py b/ldapserver/schema/rfc4519/attribute_types.py deleted file mode 100644 index d4e474562a577ae1b456d6dfbb45221f8e5212de..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4519/attribute_types.py +++ /dev/null @@ -1,96 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import AttributeType -from ..rfc4512.attribute_types import * -from . import syntaxes, matching_rules - -name = AttributeType('2.5.4.41', name='name', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) # Defined first, so sup=name works -businessCategory = AttributeType('2.5.4.15', name='businessCategory', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -c = AttributeType('2.5.4.6', name='c', sup=name, syntax=syntaxes.CountryString(), single_value=True) -cn = AttributeType('2.5.4.3', name='cn', sup=name) -dc = AttributeType('0.9.2342.19200300.100.1.25', name='dc', equality=matching_rules.caseIgnoreIA5Match, substr=matching_rules.caseIgnoreIA5SubstringsMatch, syntax=syntaxes.IA5String(), single_value=True) -description = AttributeType('2.5.4.13', name='description', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -destinationIndicator = AttributeType('2.5.4.27', name='destinationIndicator', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.PrintableString()) -distinguishedName = AttributeType('2.5.4.49', name='distinguishedName', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN()) -dnQualifier = AttributeType('2.5.4.46', name='dnQualifier', equality=matching_rules.caseIgnoreMatch, ordering=matching_rules.caseIgnoreOrderingMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.PrintableString()) -enhancedSearchGuide = AttributeType('2.5.4.47', name='enhancedSearchGuide', syntax=syntaxes.EnhancedGuide()) -facsimileTelephoneNumber = AttributeType('2.5.4.23', name='facsimileTelephoneNumber', syntax=syntaxes.FacsimileTelephoneNumber()) -generationQualifier = AttributeType('2.5.4.44', name='generationQualifier', sup=name) -givenName = AttributeType('2.5.4.42', name='givenName', sup=name) -houseIdentifier = AttributeType('2.5.4.51', name='houseIdentifier', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -initials = AttributeType('2.5.4.43', name='initials', sup=name) -internationalISDNNumber = AttributeType('2.5.4.25', name='internationalISDNNumber', equality=matching_rules.numericStringMatch, substr=matching_rules.numericStringSubstringsMatch, syntax=syntaxes.NumericString()) -l = AttributeType('2.5.4.7', name='l', sup=name) -member = AttributeType('2.5.4.31', name='member', sup=distinguishedName) -o = AttributeType('2.5.4.10', name='o', sup=name) -ou = AttributeType('2.5.4.11', name='ou', sup=name) -owner = AttributeType('2.5.4.32', name='owner', sup=distinguishedName) -physicalDeliveryOfficeName = AttributeType('2.5.4.19', name='physicalDeliveryOfficeName', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -postalAddress = AttributeType('2.5.4.16', name='postalAddress', equality=matching_rules.caseIgnoreListMatch, substr=matching_rules.caseIgnoreListSubstringsMatch, syntax=syntaxes.PostalAddress()) -postalCode = AttributeType('2.5.4.17', name='postalCode', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -postOfficeBox = AttributeType('2.5.4.18', name='postOfficeBox', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -preferredDeliveryMethod = AttributeType('2.5.4.28', name='preferredDeliveryMethod', syntax=syntaxes.DeliveryMethod(), single_value=True) -registeredAddress = AttributeType('2.5.4.26', name='registeredAddress', sup=postalAddress, syntax=syntaxes.PostalAddress()) -roleOccupant = AttributeType('2.5.4.33', name='roleOccupant', sup=distinguishedName) -searchGuide = AttributeType('2.5.4.14', name='searchGuide', syntax=syntaxes.Guide()) -seeAlso = AttributeType('2.5.4.34', name='seeAlso', sup=distinguishedName) -serialNumber = AttributeType('2.5.4.5', name='serialNumber', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.PrintableString()) -sn = AttributeType('2.5.4.4', name='sn', sup=name) -st = AttributeType('2.5.4.8', name='st', sup=name) -street = AttributeType('2.5.4.9', name='street', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -telephoneNumber = AttributeType('2.5.4.20', name='telephoneNumber', equality=matching_rules.telephoneNumberMatch, substr=matching_rules.telephoneNumberSubstringsMatch, syntax=syntaxes.TelephoneNumber()) -teletexTerminalIdentifier = AttributeType('2.5.4.22', name='teletexTerminalIdentifier', syntax=syntaxes.TeletexTerminalIdentifier()) -telexNumber = AttributeType('2.5.4.21', name='telexNumber', syntax=syntaxes.TelexNumber()) -title = AttributeType('2.5.4.12', name='title', sup=name) -uid = AttributeType('0.9.2342.19200300.100.1.1', name='uid', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -uniqueMember = AttributeType('2.5.4.50', name='uniqueMember', equality=matching_rules.uniqueMemberMatch, syntax=syntaxes.NameAndOptionalUID()) -userPassword = AttributeType('2.5.4.35', name='userPassword', equality=matching_rules.octetStringMatch, syntax=syntaxes.OctetString()) -x121Address = AttributeType('2.5.4.24', name='x121Address', equality=matching_rules.numericStringMatch, substr=matching_rules.numericStringSubstringsMatch, syntax=syntaxes.NumericString()) -x500UniqueIdentifier = AttributeType('2.5.4.45', name='x500UniqueIdentifier', equality=matching_rules.bitStringMatch, syntax=syntaxes.BitString()) - -ALL = ALL + ( - name, - businessCategory, - c, - cn, - dc, - description, - destinationIndicator, - distinguishedName, - dnQualifier, - enhancedSearchGuide, - facsimileTelephoneNumber, - generationQualifier, - givenName, - houseIdentifier, - initials, - internationalISDNNumber, - l, - member, - o, - ou, - owner, - physicalDeliveryOfficeName, - postalAddress, - postalCode, - postOfficeBox, - preferredDeliveryMethod, - registeredAddress, - roleOccupant, - searchGuide, - seeAlso, - serialNumber, - sn, - st, - street, - telephoneNumber, - teletexTerminalIdentifier, - telexNumber, - title, - uid, - uniqueMember, - userPassword, - x121Address, - x500UniqueIdentifier, -) diff --git a/ldapserver/schema/rfc4519/matching_rules.py b/ldapserver/schema/rfc4519/matching_rules.py deleted file mode 100644 index 1ef1df999613338c3ced480a6bdb1256c03963f8..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4519/matching_rules.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4512.matching_rules import * diff --git a/ldapserver/schema/rfc4519/object_classes.py b/ldapserver/schema/rfc4519/object_classes.py deleted file mode 100644 index 412bc590c95868f4fa5889edea28ae0b78166daf..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4519/object_classes.py +++ /dev/null @@ -1,38 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import ObjectClass, ObjectClassKind -from ..rfc4512.object_classes import * -from . import attribute_types - -person = ObjectClass('2.5.6.6', name='person', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.sn, attribute_types.cn], may=[attribute_types.userPassword, attribute_types.telephoneNumber, attribute_types.seeAlso, attribute_types.description]) # defined first, so sup=person works -applicationProcess = ObjectClass('2.5.6.11', name='applicationProcess', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.cn], may=[attribute_types.seeAlso, attribute_types.ou, attribute_types.l, attribute_types.description]) -country = ObjectClass('2.5.6.2', name='country', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.c], may=[attribute_types.searchGuide, attribute_types.description]) -dcObject = ObjectClass('1.3.6.1.4.1.1466.344', name='dcObject', sup=top, kind=ObjectClassKind.AUXILIARY, must=[attribute_types.dc]) -device = ObjectClass('2.5.6.14', name='device', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.cn], may=[attribute_types.serialNumber, attribute_types.seeAlso, attribute_types.owner, attribute_types.ou, attribute_types.o, attribute_types.l, attribute_types.description]) -groupOfNames = ObjectClass('2.5.6.9', name='groupOfNames', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.member, attribute_types.cn], may=[attribute_types.businessCategory, attribute_types.seeAlso, attribute_types.owner, attribute_types.ou, attribute_types.o, attribute_types.description]) -groupOfUniqueNames = ObjectClass('2.5.6.17', name='groupOfUniqueNames', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.uniqueMember, attribute_types.cn], may=[attribute_types.businessCategory, attribute_types.seeAlso, attribute_types.owner, attribute_types.ou, attribute_types.o, attribute_types.description]) -locality = ObjectClass('2.5.6.3', name='locality', sup=top, kind=ObjectClassKind.STRUCTURAL, may=[attribute_types.street, attribute_types.seeAlso, attribute_types.searchGuide, attribute_types.st, attribute_types.l, attribute_types.description]) -organization = ObjectClass('2.5.6.4', name='organization', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.o], may=[attribute_types.userPassword, attribute_types.searchGuide, attribute_types.seeAlso, attribute_types.businessCategory, attribute_types.x121Address, attribute_types.registeredAddress, attribute_types.destinationIndicator, attribute_types.preferredDeliveryMethod, attribute_types.telexNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telephoneNumber, attribute_types.internationalISDNNumber, attribute_types.facsimileTelephoneNumber, attribute_types.street, attribute_types.postOfficeBox, attribute_types.postalCode, attribute_types.postalAddress, attribute_types.physicalDeliveryOfficeName, attribute_types.st, attribute_types.l, attribute_types.description]) -organizationalPerson = ObjectClass('2.5.6.7', name='organizationalPerson', sup=person, kind=ObjectClassKind.STRUCTURAL, may=[attribute_types.title, attribute_types.x121Address, attribute_types.registeredAddress, attribute_types.destinationIndicator, attribute_types.preferredDeliveryMethod, attribute_types.telexNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telephoneNumber, attribute_types.internationalISDNNumber, attribute_types.facsimileTelephoneNumber, attribute_types.street, attribute_types.postOfficeBox, attribute_types.postalCode, attribute_types.postalAddress, attribute_types.physicalDeliveryOfficeName, attribute_types.ou, attribute_types.st, attribute_types.l]) -organizationalRole = ObjectClass('2.5.6.8', name='organizationalRole', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.cn], may=[attribute_types.x121Address, attribute_types.registeredAddress, attribute_types.destinationIndicator, attribute_types.preferredDeliveryMethod, attribute_types.telexNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telephoneNumber, attribute_types.internationalISDNNumber, attribute_types.facsimileTelephoneNumber, attribute_types.seeAlso, attribute_types.roleOccupant, attribute_types.preferredDeliveryMethod, attribute_types.street, attribute_types.postOfficeBox, attribute_types.postalCode, attribute_types.postalAddress, attribute_types.physicalDeliveryOfficeName, attribute_types.ou, attribute_types.st, attribute_types.l, attribute_types.description]) -organizationalUnit = ObjectClass('2.5.6.5', name='organizationalUnit', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.ou], may=[attribute_types.businessCategory, attribute_types.description, attribute_types.destinationIndicator, attribute_types.facsimileTelephoneNumber, attribute_types.internationalISDNNumber, attribute_types.l, attribute_types.physicalDeliveryOfficeName, attribute_types.postalAddress, attribute_types.postalCode, attribute_types.postOfficeBox, attribute_types.preferredDeliveryMethod, attribute_types.registeredAddress, attribute_types.searchGuide, attribute_types.seeAlso, attribute_types.st, attribute_types.street, attribute_types.telephoneNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telexNumber, attribute_types.userPassword, attribute_types.x121Address]) -residentialPerson = ObjectClass('2.5.6.10', name='residentialPerson', sup=person, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.l], may=[attribute_types.businessCategory, attribute_types.x121Address, attribute_types.registeredAddress, attribute_types.destinationIndicator, attribute_types.preferredDeliveryMethod, attribute_types.telexNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telephoneNumber, attribute_types.internationalISDNNumber, attribute_types.facsimileTelephoneNumber, attribute_types.preferredDeliveryMethod, attribute_types.street, attribute_types.postOfficeBox, attribute_types.postalCode, attribute_types.postalAddress, attribute_types.physicalDeliveryOfficeName, attribute_types.st, attribute_types.l]) -uidObject = ObjectClass('1.3.6.1.1.3.1', name='uidObject', sup=top, kind=ObjectClassKind.AUXILIARY, must=[attribute_types.uid]) - -ALL = ALL + ( - person, - applicationProcess, - country, - dcObject, - device, - groupOfNames, - groupOfUniqueNames, - locality, - organization, - organizationalPerson, - organizationalRole, - organizationalUnit, - residentialPerson, - uidObject, -) diff --git a/ldapserver/schema/rfc4519/syntaxes.py b/ldapserver/schema/rfc4519/syntaxes.py deleted file mode 100644 index d6e0781b244310e4ab07d3136edf420256c5aee5..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4519/syntaxes.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4512.syntaxes import * diff --git a/ldapserver/schema/rfc4523/__init__.py b/ldapserver/schema/rfc4523/__init__.py deleted file mode 100644 index ecb7ec89295fc079b08100f4d211d70118dba022..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4523/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types diff --git a/ldapserver/schema/rfc4523/attribute_types.py b/ldapserver/schema/rfc4523/attribute_types.py deleted file mode 100644 index f8585d5ae35dcd269823309db934245e94640487..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4523/attribute_types.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..types import AttributeType -from . import syntaxes, matching_rules - -userCertificate = AttributeType('2.5.4.36', name='userCertificate', desc='X.509 user certificate', equality=matching_rules.certificateExactMatch, syntax=syntaxes.X509Certificate()) - -ALL = ( - userCertificate, -) diff --git a/ldapserver/schema/rfc4523/matching_rules.py b/ldapserver/schema/rfc4523/matching_rules.py deleted file mode 100644 index a556255424018e8bcc6b7998ba3502902733fff2..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4523/matching_rules.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..types import MatchingRule -from . import syntaxes - -certificateExactMatch = MatchingRule('2.5.13.34', name='certificateExactMatch', desc='X.509 Certificate Exact Match', syntax=syntaxes.X509CertificateExactAssertion()) - -ALL = ( - certificateExactMatch, -) diff --git a/ldapserver/schema/rfc4523/syntaxes.py b/ldapserver/schema/rfc4523/syntaxes.py deleted file mode 100644 index 851edc685ca7775b1fab98aa7ec87e4c33ac998c..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4523/syntaxes.py +++ /dev/null @@ -1,14 +0,0 @@ -from ..rfc4517.syntaxes import BytesSyntax - -class X509Certificate(BytesSyntax): - oid = '1.3.6.1.4.1.1466.115.121.1.8' - desc = 'X.509 Certificate' - -class X509CertificateExactAssertion(BytesSyntax): - oid = '1.3.6.1.1.15.1' - desc = 'X.509 Certificate Exact Assertion' - -ALL = ( - X509Certificate, - X509CertificateExactAssertion, -) diff --git a/ldapserver/schema/rfc4524/__init__.py b/ldapserver/schema/rfc4524/__init__.py deleted file mode 100644 index 1727734f9ec5518c83e74fb73658a6a8325b070f..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4524/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import syntaxes, matching_rules, attribute_types, object_classes diff --git a/ldapserver/schema/rfc4524/attribute_types.py b/ldapserver/schema/rfc4524/attribute_types.py deleted file mode 100644 index bdd2c687fff69b9f2dfc58ae99c95555964c1f4a..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4524/attribute_types.py +++ /dev/null @@ -1,60 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import AttributeType -from ..rfc4519.attribute_types import * -from . import syntaxes, matching_rules - -associatedDomain = AttributeType('0.9.2342.19200300.100.1.37', name='associatedDomain', equality=matching_rules.caseIgnoreIA5Match, substr=matching_rules.caseIgnoreIA5SubstringsMatch, syntax=syntaxes.IA5String()) -associatedName = AttributeType('0.9.2342.19200300.100.1.38', name='associatedName', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN()) -buildingName = AttributeType('0.9.2342.19200300.100.1.48', name='buildingName', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -co = AttributeType('0.9.2342.19200300.100.1.43', name='co', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -documentAuthor = AttributeType('0.9.2342.19200300.100.1.14', name='documentAuthor', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN()) -documentIdentifier = AttributeType('0.9.2342.19200300.100.1.11', name='documentIdentifier', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -documentLocation = AttributeType('0.9.2342.19200300.100.1.15', name='documentLocation', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -documentPublisher = AttributeType('0.9.2342.19200300.100.1.56', name='documentPublisher', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString()) -documentTitle = AttributeType('0.9.2342.19200300.100.1.12', name='documentTitle', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -documentVersion = AttributeType('0.9.2342.19200300.100.1.13', name='documentVersion', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -drink = AttributeType('0.9.2342.19200300.100.1.5', name='drink', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -homePhone = AttributeType('0.9.2342.19200300.100.1.20', name='homePhone', equality=matching_rules.telephoneNumberMatch, substr=matching_rules.telephoneNumberSubstringsMatch, syntax=syntaxes.TelephoneNumber()) -homePostalAddress = AttributeType('0.9.2342.19200300.100.1.39', name='homePostalAddress', equality=matching_rules.caseIgnoreListMatch, substr=matching_rules.caseIgnoreListSubstringsMatch, syntax=syntaxes.PostalAddress()) -host = AttributeType('0.9.2342.19200300.100.1.9', name='host', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -info = AttributeType('0.9.2342.19200300.100.1.4', name='info', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(2048)) -mail = AttributeType('0.9.2342.19200300.100.1.3', name='mail', equality=matching_rules.caseIgnoreIA5Match, substr=matching_rules.caseIgnoreIA5SubstringsMatch, syntax=syntaxes.IA5String(256)) -manager = AttributeType('0.9.2342.19200300.100.1.10', name='manager', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN()) -mobile = AttributeType('0.9.2342.19200300.100.1.41', name='mobile', equality=matching_rules.telephoneNumberMatch, substr=matching_rules.telephoneNumberSubstringsMatch, syntax=syntaxes.TelephoneNumber()) -organizationalStatus = AttributeType('0.9.2342.19200300.100.1.45', name='organizationalStatus', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -pager = AttributeType('0.9.2342.19200300.100.1.42', name='pager', equality=matching_rules.telephoneNumberMatch, substr=matching_rules.telephoneNumberSubstringsMatch, syntax=syntaxes.TelephoneNumber()) -personalTitle = AttributeType('0.9.2342.19200300.100.1.40', name='personalTitle', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -roomNumber = AttributeType('0.9.2342.19200300.100.1.6', name='roomNumber', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -secretary = AttributeType('0.9.2342.19200300.100.1.21', name='secretary', equality=matching_rules.distinguishedNameMatch, syntax=syntaxes.DN()) -uniqueIdentifier = AttributeType('0.9.2342.19200300.100.1.44', name='uniqueIdentifier', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) -UserClass = AttributeType('0.9.2342.19200300.100.1.8', name='userClass', equality=matching_rules.caseIgnoreMatch, substr=matching_rules.caseIgnoreSubstringsMatch, syntax=syntaxes.DirectoryString(256)) - -ALL = ALL + ( - associatedDomain, - associatedName, - buildingName, - co, - documentAuthor, - documentIdentifier, - documentLocation, - documentPublisher, - documentTitle, - documentVersion, - drink, - homePhone, - homePostalAddress, - host, - info, - mail, - manager, - mobile, - organizationalStatus, - pager, - personalTitle, - roomNumber, - secretary, - uniqueIdentifier, - UserClass, -) diff --git a/ldapserver/schema/rfc4524/matching_rules.py b/ldapserver/schema/rfc4524/matching_rules.py deleted file mode 100644 index e3a7ec210d1eafac6d3748cb9abd780d71ea094c..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4524/matching_rules.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4519.matching_rules import * diff --git a/ldapserver/schema/rfc4524/object_classes.py b/ldapserver/schema/rfc4524/object_classes.py deleted file mode 100644 index 584a0cf267cfebd0bf89a63e62fcbc617f520a99..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4524/object_classes.py +++ /dev/null @@ -1,27 +0,0 @@ -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..types import ObjectClass, ObjectClassKind -from ..rfc4519.object_classes import * -from . import attribute_types - -account = ObjectClass('0.9.2342.19200300.100.4.5', name='account', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.uid], may=[attribute_types.description, attribute_types.seeAlso, attribute_types.l, attribute_types.o, attribute_types.ou, attribute_types.host] ) -document = ObjectClass('0.9.2342.19200300.100.4.6', name='document', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.documentIdentifier], may=[attribute_types.cn, attribute_types.description, attribute_types.seeAlso, attribute_types.l, attribute_types.o, attribute_types.ou, attribute_types.documentTitle, attribute_types.documentVersion, attribute_types.documentAuthor, attribute_types.documentLocation, attribute_types.documentPublisher] ) -documentSeries = ObjectClass('0.9.2342.19200300.100.4.9', name='documentSeries', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.cn], may=[attribute_types.description, attribute_types.l, attribute_types.o, attribute_types.ou, attribute_types.seeAlso, attribute_types.telephoneNumber] ) -domain = ObjectClass('0.9.2342.19200300.100.4.13', name='domain', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.dc], may=[attribute_types.userPassword, attribute_types.searchGuide, attribute_types.seeAlso, attribute_types.businessCategory, attribute_types.x121Address, attribute_types.registeredAddress, attribute_types.destinationIndicator, attribute_types.preferredDeliveryMethod, attribute_types.telexNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telephoneNumber, attribute_types.internationalISDNNumber, attribute_types.facsimileTelephoneNumber, attribute_types.street, attribute_types.postOfficeBox, attribute_types.postalCode, attribute_types.postalAddress, attribute_types.physicalDeliveryOfficeName, attribute_types.st, attribute_types.l, attribute_types.description, attribute_types.o, attribute_types.associatedName] ) -domainRelatedObject = ObjectClass('0.9.2342.19200300.100.4.17', name='domainRelatedObject', sup=top, kind=ObjectClassKind.AUXILIARY, must=[attribute_types.associatedDomain]) -friendlyCountry = ObjectClass('0.9.2342.19200300.100.4.18', name='friendlyCountry', sup=country, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.co]) -rFC822localPart = ObjectClass('0.9.2342.19200300.100.4.14', name='rFC822localPart', sup=domain, kind=ObjectClassKind.STRUCTURAL, may=[attribute_types.cn, attribute_types.description, attribute_types.destinationIndicator, attribute_types.facsimileTelephoneNumber, attribute_types.internationalISDNNumber, attribute_types.physicalDeliveryOfficeName, attribute_types.postalAddress, attribute_types.postalCode, attribute_types.postOfficeBox, attribute_types.preferredDeliveryMethod, attribute_types.registeredAddress, attribute_types.seeAlso, attribute_types.sn, attribute_types.street, attribute_types.telephoneNumber, attribute_types.teletexTerminalIdentifier, attribute_types.telexNumber, attribute_types.x121Address] ) -room = ObjectClass('0.9.2342.19200300.100.4.7', name='room', sup=top, kind=ObjectClassKind.STRUCTURAL, must=[attribute_types.cn], may=[attribute_types.roomNumber, attribute_types.description, attribute_types.seeAlso, attribute_types.telephoneNumber] ) -simpleSecurityObject = ObjectClass('0.9.2342.19200300.100.4.19', name='simpleSecurityObject', sup=top, kind=ObjectClassKind.AUXILIARY, must=[attribute_types.userPassword]) - -ALL = ALL + ( - account, - document, - documentSeries, - domain, - domainRelatedObject, - friendlyCountry, - rFC822localPart, - room, - simpleSecurityObject, -) diff --git a/ldapserver/schema/rfc4524/syntaxes.py b/ldapserver/schema/rfc4524/syntaxes.py deleted file mode 100644 index c26e86963cec30db1c1c0f9acb9aed14b667cc3d..0000000000000000000000000000000000000000 --- a/ldapserver/schema/rfc4524/syntaxes.py +++ /dev/null @@ -1,4 +0,0 @@ - -# pylint: disable=wildcard-import,unused-wildcard-import - -from ..rfc4519.syntaxes import * diff --git a/ldapserver/schema/syntaxes.py b/ldapserver/schema/syntaxes.py new file mode 100644 index 0000000000000000000000000000000000000000..a17e1a29155327f43c161d01266d0ade65c974fe --- /dev/null +++ b/ldapserver/schema/syntaxes.py @@ -0,0 +1,356 @@ +import re +import datetime + +from .definitions import SyntaxDefinition +from .. import dn, exceptions + +class BytesSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + return value + + def decode(self, schema, raw_value): + return raw_value + +class StringSyntaxDefinition(SyntaxDefinition): + def __init__(self, oid, encoding='utf-8', re_pattern='.*', **kwargs): + super().__init__(oid, **kwargs) + self.encoding = encoding + self.re_pattern = re.compile(re_pattern) + + def encode(self, schema, value): + return value.encode(self.encoding) + + def decode(self, schema, raw_value): + try: + value = raw_value.decode(self.encoding) + except UnicodeDecodeError as exc: + raise exceptions.LDAPInvalidAttributeSyntax(exc.reason) from exc + if not re.fullmatch(self.re_pattern, value): + raise exceptions.LDAPInvalidAttributeSyntax() + return value + +class IntegerSyntaxDefinition(StringSyntaxDefinition): + def __init__(self, oid, **kwargs): + super().__init__(oid, encoding='ascii', re_pattern='([0-9]|-?[1-9][0-9]+)', **kwargs) + + def encode(self, schema, value): + return super().encode(schema, str(value)) + + def decode(self, schema, raw_value): + try: + return int(super().decode(schema, raw_value)) + except ValueError as exc: + raise exceptions.LDAPInvalidAttributeSyntax() from exc + +class SchemaElementSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + return str(value).encode('utf8') + +class BooleanSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + return b'TRUE' if value else b'FALSE' + + def decode(self, schema, raw_value): + if raw_value == b'TRUE': + return True + elif raw_value == b'FALSE': + return False + else: + raise exceptions.LDAPInvalidAttributeSyntax() + +class DNSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + return str(value).encode('utf8') + + def decode(self, schema, raw_value): + try: + return dn.DN.from_str(raw_value.decode('utf8')) + except (UnicodeDecodeError, TypeError, ValueError) as exc: + raise exceptions.LDAPInvalidAttributeSyntax() from exc + +class NameAndOptionalUIDSyntaxDefinition(StringSyntaxDefinition): + def encode(self, schema, value): + return str(value).encode('utf8') + + def decode(self, schema, raw_value): + try: + return dn.DNWithUID.from_str(raw_value.decode('utf8')) + except (UnicodeDecodeError, TypeError, ValueError) as exc: + raise exceptions.LDAPInvalidAttributeSyntax() from exc + +class GeneralizedTimeSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + if value.microsecond: + str_value = value.strftime('%Y%m%d%H%M%S.%f') + elif value.second: + str_value = value.strftime('%Y%m%d%H%M%S') + else: + str_value = value.strftime('%Y%m%d%H%M') + if value.tzinfo == datetime.timezone.utc: + str_value += 'Z' + elif value.tzinfo is not None: + delta_seconds = value.tzinfo.utcoffset(value).total_seconds() + if delta_seconds < 0: + str_value += '-' + delta_seconds = -delta_seconds + else: + str_value += '+' + hour = delta_seconds // 3600 + minute = (delta_seconds % 3600) // 60 + str_value += '%02d%02d'%(hour, minute) + return str_value.encode('ascii') + + def decode(self, schema, raw_value): + try: + raw_value = raw_value.decode('utf8') + except UnicodeDecodeError as exc: + raise exceptions.LDAPInvalidAttributeSyntax() from exc + match = re.fullmatch(r'([0-9]{10})(|[0-9]{2}|[0-9]{4})(|[,.][0-9]+)(Z|[+-][0-9]{2}|[+-][0-9]{4})', raw_value) + if match is None: + raise exceptions.LDAPInvalidAttributeSyntax() + main, minute_second, fraction, timezone = match.groups() + fraction = float('0.' + (fraction[1:] or '0')) + result = datetime.datetime.strptime(main, '%Y%m%d%H') + if not minute_second: + result += datetime.timedelta(hours=fraction) + if len(minute_second) == 2: + result += datetime.timedelta(minutes=int(minute_second)+fraction) + elif len(minute_second) == 4: + minute = minute_second[:2] + second = minute_second[2:4] + result += datetime.timedelta(minutes=int(minute), seconds=int(second)+fraction) + if timezone == 'Z': + result = result.replace(tzinfo=datetime.timezone.utc) + elif timezone: + sign, hour, minute = timezone[0], timezone[1:3], (timezone[3:5] or '00') + delta = datetime.timedelta(hours=int(hour), minutes=int(minute)) + if sign == '+': + result = result.replace(tzinfo=datetime.timezone(delta)) + else: + result = result.replace(tzinfo=datetime.timezone(-delta)) + return result + +class PostalAddressSyntaxDefinition(SyntaxDefinition): + # 3.3.28. Postal Address + # + # A value of the Postal Address syntax is a sequence of strings of one + # or more arbitrary UCS characters, which form an address in a physical + # mail system. + # + # The LDAP-specific encoding of a value of this syntax is defined by + # the following ABNF: + # + # + # PostalAddress = line *( DOLLAR line ) + # line = 1*line-char + # line-char = %x00-23 + # / (%x5C "24") ; escaped "$" + # / %x25-5B + # / (%x5C "5C") ; escaped "\" + # / %x5D-7F + # / UTFMB + # + # Each character string (i.e., <line>) of a postal address value is + # encoded as a UTF-8 [RFC3629] string, except that "\" and "$" + # characters, if they occur in the string, are escaped by a "\" + # character followed by the two hexadecimal digit code for the + # character. The <DOLLAR> and <UTFMB> rules are defined in [RFC4512]. + # + # Many servers limit the postal address to no more than six lines of no + # more than thirty characters each. + # + # Example: + # 1234 Main St.$Anytown, CA 12345$USA + # \241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA + # + # The LDAP definition for the Postal Address syntax is: + # + # ( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' ) + # + # This syntax corresponds to the PostalAddress ASN.1 type from [X.520]; + # that is + # + # PostalAddress ::= SEQUENCE SIZE(1..ub-postal-line) OF + # DirectoryString { ub-postal-string } + # + # The values of ub-postal-line and ub-postal-string (both integers) are + # implementation defined. Non-normative definitions appear in [X.520]. + + # Native values are lists of str + def encode(self, schema, value): + return '$'.join([line.replace('\\', '\\5C').replace('$', '\\24') for line in value]).encode('utf8') + + def decode(self, schema, raw_value): + return [line.replace('\\24', '$').replace('\\5C', '\\') for line in raw_value.decode('utf8').split('$')] + +class SubstringAssertionSyntaxDefinition(SyntaxDefinition): + # Native values are lists of str + def encode(self, schema, value): + raise NotImplementedError() + + def decode(self, schema, raw_value): + value = raw_value.decode('utf8') + if '*' not in value: + raise exceptions.LDAPInvalidAttributeSyntax() + substrings = [substring.replace('\\2A', '*').replace('\\5C', '\\') for substring in value.split('*')] + initial_substring, *any_substring, final_substring = substrings + return (initial_substring or None, any_substring, final_substring or None) + +class UTCTimeSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + if value.second: + str_value = value.strftime('%y%m%d%H%M%S') + else: + str_value = value.strftime('%y%m%d%H%M') + if value.tzinfo == datetime.timezone.utc: + str_value += 'Z' + elif value.tzinfo is not None: + delta_seconds = value.tzinfo.utcoffset(value).total_seconds() + if delta_seconds < 0: + str_value += '-' + delta_seconds = -delta_seconds + else: + str_value += '+' + hour = delta_seconds // 3600 + minute = (delta_seconds % 3600) // 60 + str_value += '%02d%02d'%(hour, minute) + return str_value.encode('ascii') + + def decode(self, schema, raw_value): + try: + raw_value = raw_value.decode('utf8') + except UnicodeDecodeError as exc: + raise exceptions.LDAPInvalidAttributeSyntax() from exc + match = re.fullmatch(r'([0-9]{10})(|[0-9]{2})(|Z|[+-][0-9]{4})', raw_value) + if match is None: + raise exceptions.LDAPInvalidAttributeSyntax() + main, seconds, timezone = match.groups() + result = datetime.datetime.strptime(main, '%y%m%d%H%M') + if seconds: + result = result.replace(second=int(seconds)) + if timezone == 'Z': + result = result.replace(tzinfo=datetime.timezone.utc) + elif timezone: + sign, hour, minute = timezone[0], timezone[1:3], timezone[3:5] + delta = datetime.timedelta(hours=int(hour), minutes=int(minute)) + if sign == '+': + result = result.replace(tzinfo=datetime.timezone(delta)) + else: + result = result.replace(tzinfo=datetime.timezone(-delta)) + return result + +class StubSyntaxDefinition(SyntaxDefinition): + def encode(self, schema, value): + raise NotImplementedError() + +# RFC2252 (deprecated legacy syntaxes required for some schemas) +Binary = BytesSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.5', desc='Binary') + +# RFC4517 +Fax = BytesSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.23', desc='Fax') +JPEG = BytesSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.28', desc='JPEG') +OctetString = BytesSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.40', desc='Octet String') +DirectoryString = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.15', desc='Directory String') +IA5String = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.26', desc='IA5 String', encoding='ascii', extra_compatability_tags=DirectoryString.compatability_tags) +PrintableString = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.44', desc='Printable String', encoding='ascii', re_pattern='[A-Za-z0-9\'()+,.=/:? -]*', extra_compatability_tags=IA5String.compatability_tags) +CountryString = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.11', desc='Country String', encoding='ascii', re_pattern='[A-Za-z0-9\'()+,.=/:? -]{2}', extra_compatability_tags=PrintableString.compatability_tags) +TelephoneNumber = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.50', desc='Telephone Number', encoding='ascii', re_pattern='[A-Za-z0-9\'()+,.=/:? -]*', extra_compatability_tags=PrintableString.compatability_tags) +OID = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.38', desc='OID', encoding='ascii', re_pattern=r'[0-9]+(\.[0-9])*|[A-Za-z][A-Za-z0-9-]*') +BitString = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.6', desc='Bit String', encoding='ascii', re_pattern='\'[01]*\'B') +DeliveryMethod = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.14', desc='Delivery Method') +FacsimileTelephoneNumber = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.22', desc='Facsimile Telephone Number') +EnhancedGuide = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.21', desc='Enhanced Guide') +Guide = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.25', desc='Guide') +NumericString = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.36', desc='Numeric String', encoding='ascii', re_pattern='[0-9 ]+') +OtherMailbox = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.39', desc='Other Mailbox') +TeletexTerminalIdentifier = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.51', desc='Teletex Terminal Identifier') +TelexNumber = StringSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.52', desc='Telex Number') +INTEGER = IntegerSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.27', desc='INTEGER') +AttributeTypeDescription = SchemaElementSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.3', desc='Attribute Type Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +LDAPSyntaxDescription = SchemaElementSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.54', desc='LDAP Syntax Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +MatchingRuleDescription = SchemaElementSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.30', desc='Matching Rule Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +MatchingRuleUseDescription = SchemaElementSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.31', desc='Matching Rule Use Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +ObjectClassDescription = SchemaElementSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.37', desc='Object Class Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +Boolean = BooleanSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.7', desc='Boolean') +DN = DNSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.12', desc='DN') +NameAndOptionalUID = NameAndOptionalUIDSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.34', desc='Name And Optional UID') +GeneralizedTime = GeneralizedTimeSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.24', desc='Generalized Time') +PostalAddress = PostalAddressSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.41', desc='Postal Address') +SubstringAssertion = SubstringAssertionSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.58', desc='Substring Assertion') +UTCTime = UTCTimeSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.53', desc='UTC Time') +DITContentRuleDescription = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.16', desc='DIT Content Rule Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) +DITStructureRuleDescription = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.17', desc='DIT Structure Rule Description', extra_compatability_tags=['FirstComponent:'+INTEGER.oid]) +NameFormDescription = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.35', desc='Name Form Description', extra_compatability_tags=['FirstComponent:'+OID.oid]) + +# RFC4523 +X509Certificate = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.8', desc='X.509 Certificate') +X509CertificateList = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.9', desc='X.509 Certificate List') +X509CertificatePair = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.10', desc='X.509 Certificate Pair') +X509SupportedAlgorithm = StubSyntaxDefinition('1.3.6.1.4.1.1466.115.121.1.49', desc='X.509 Supported Algorithm') +X509CertificateExactAssertion = StubSyntaxDefinition('1.3.6.1.1.15.1', desc='X.509 Certificate Exact Assertion') +X509CertificateAssertion = StubSyntaxDefinition('1.3.6.1.1.15.2', desc='X.509 Certificate Assertion') +X509CertificatePairExactAssertion = StubSyntaxDefinition('1.3.6.1.1.15.3', desc='X.509 Certificate Pair Exact Assertion') +X509CertificatePairAssertion = StubSyntaxDefinition('1.3.6.1.1.15.4', desc='X.509 Certificate Pair Assertion') +X509CertificateListExactAssertion = StubSyntaxDefinition('1.3.6.1.1.15.5', desc='X.509 Certificate List Exact Assertion') +X509CertificateListAssertion = StubSyntaxDefinition('1.3.6.1.1.15.6', desc='X.509 Certificate List Assertion') +X509AlgorithmIdentifier = StubSyntaxDefinition('1.3.6.1.1.15.7', desc='X.509 Algorithm Identifier') + +# RFC3112 +AuthPasswordSyntax = StubSyntaxDefinition('1.3.6.1.4.1.4203.1.1.2', desc='authentication password syntax') + +ALL = ( + # RFC2252 + Binary, + + # RFC4517 + AttributeTypeDescription, + BitString, + Boolean, + CountryString, + DeliveryMethod, + DirectoryString, + DITContentRuleDescription, + DITStructureRuleDescription, + DN, + EnhancedGuide, + FacsimileTelephoneNumber, + Fax, + GeneralizedTime, + Guide, + IA5String, + INTEGER, + JPEG, + LDAPSyntaxDescription, + MatchingRuleDescription, + MatchingRuleUseDescription, + NameAndOptionalUID, + NameFormDescription, + NumericString, + ObjectClassDescription, + OctetString, + OID, + OtherMailbox, + PostalAddress, + PrintableString, + SubstringAssertion, + TelephoneNumber, + TeletexTerminalIdentifier, + TelexNumber, + UTCTime, + + # RFC4523 + X509Certificate, + X509CertificateList, + X509CertificatePair, + X509SupportedAlgorithm, + X509CertificateExactAssertion, + X509CertificateAssertion, + X509CertificatePairExactAssertion, + X509CertificatePairAssertion, + X509CertificateListExactAssertion, + X509CertificateListAssertion, + X509AlgorithmIdentifier, + + # RFC3112 + AuthPasswordSyntax, +) diff --git a/ldapserver/schema/types.py b/ldapserver/schema/types.py index 3af0c061fa68f8a82ca4043ba4a8efa1a9bfbb09..c5f24df9aa56010db5d8025d55e8c597598b667f 100644 --- a/ldapserver/schema/types.py +++ b/ldapserver/schema/types.py @@ -1,452 +1,383 @@ -import enum -import re - -def escape(string): - result = '' - for char in string: - if char == '\'': - result += '\\27' - elif char == '\\': - result += '\\5C' - else: - result += char - return result +# pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals -class Syntax: - '''LDAP syntax for attribute and assertion values - - Instances of the class represent (optionally length-constrained) syntax - refercences as used with `MatchingRule` and `AttributeType`.''' - oid: str - desc: str - - def __init__(self, max_len=None): - self.max_len = max_len - if max_len is None: - self.ref = self.oid - else: - self.ref = self.oid + '{' + str(max_len) + '}' - - @classmethod - def get_first_component_oid(cls): - '''Used by objectIdentifierFirstComponentMatch''' - return cls.oid - - @classmethod - def to_definition(cls): - '''Get string reperesentation as used in ldapSyntaxes attribute''' - return f"( {cls.oid} DESC '{escape(cls.desc)}' )" - - def decode(self, schema, raw_value): - '''Decode LDAP-specific encoding of a value to a native value - - :param schema: Schema of the object in whose context decoding takes place - :type schema: Schema - :param raw_value: LDAP-specific encoding of the value - :type raw_value: bytes - - :returns: native value (depends on syntax), None if raw_value is invalid - :rtype: any or None''' - return None - - def encode(self, schema, value): - '''Encode native value to its LDAP-specific encoding - - :param schema: Schema of the object in whose context encoding takes place - :type schema: Schema - :param value: native value (depends on syntax) - :type value: any - - :returns: LDAP-specific encoding of the value - :rtype: bytes''' - raise NotImplementedError() +import collections.abc -class MatchingRule: - '''Matching rules define how to compare attribute with assertion values - - Instances provide a number of methods to compare a single attribute value - to an assertion value. Attribute values are passed as the native type of - the attribute type's syntax. Assertion values are always decoded with the - matching rule's syntax before being passed to a matching method. - - LDAP differenciates between EQUALITY, SUBSTR and ORDERING matching rules. - This class is used for all of them. Inappropriate matching methods should - always return None. ''' - def __init__(self, oid, name, syntax, **kwargs): - self.oid = oid - self.name = name - self.names = [name] - self.syntax = syntax - for key, value in kwargs.items(): - setattr(self, key, value) - - def to_definition(self): - '''Get string reperesentation as used in matchingRules attribute''' - return f"( {self.oid} NAME '{escape(self.name)}' SYNTAX {self.syntax.ref} )" - - def get_first_component_oid(self): - '''Used by objectIdentifierFirstComponentMatch''' - return self.oid +from .definitions import MatchingRuleKind, AttributeTypeUsage, MatchingRuleUseDefinition, AttributeTypeDefinition, ObjectClassDefinition +from .. import exceptions - def __repr__(self): - return f'<ldapserver.schema.MatchingRule {self.oid}>' +__all__ = [ + 'Syntax', + 'EqualityMatchingRule', + 'OrderingMatchingRule', + 'SubstrMatchingRule', + 'AttributeType', + 'ObjectClass', + 'Schema', +] - def match_equal(self, schema, attribute_value, assertion_value): - '''Return whether attribute value is equal to assertion values +class Syntax: + '''LDAP syntax for attribute and assertion values''' + def __init__(self, schema, definition, syntaxes_by_tag): + self.schema = schema + self.definition = definition + self.oid = definition.oid + self.ref = self.oid + schema._register(self, self.oid, self.ref) + schema.syntaxes._register(self, self.oid, self.ref) + for tag in definition.compatability_tags: + syntaxes_by_tag.setdefault(tag, set()).add(self) + # Populated by MatchingRule.__init__ + self.compatible_matching_rules = set() - Only available for EQUALITY matching rules. + def __repr__(self): + return f'<ldapserver.schema.Syntax {self.oid}>' - :returns: True if attribute value matches the assertion value, False if it - does not match. If the result is Undefined, None is returned.''' - return None + def encode(self, value): + return self.definition.encode(self.schema, value) - def match_approx(self, schema, attribute_value, assertion_value): - '''Return whether attribute value is approximatly equal to assertion values + def decode(self, raw_value): + return self.definition.decode(self.schema, raw_value) - Only available for EQUALITY matching rules. +class MatchingRule: + def __init__(self, schema, definition, syntaxes_by_tag): + self.schema = schema + self.definition = definition + self.oid = definition.oid + self.syntax = schema.syntaxes[definition.syntax] + self.names = self.definition.name + self.ref = self.names[0] if self.names else self.oid + schema._register(self, self.oid, self.ref, *self.names) + schema.matching_rules._register(self, self.oid, self.ref, *self.names) + self.compatible_syntaxes = syntaxes_by_tag.setdefault(definition.compatability_tag, set()) + for syntax in self.compatible_syntaxes: + syntax.compatible_matching_rules.add(self) + # Populated by AttributeType.__init__ + self.compatible_attribute_types = set() + + def match_extensible(self, attribute_values, assertion_value): + raise exceptions.LDAPInappropriateMatching() + +class EqualityMatchingRule(MatchingRule): + def __repr__(self): + return f'<ldapserver.schema.EqualityMatchingRule {self.ref}>' - :returns: True if attribute value matches the assertion value, False if it - does not match. If the result is Undefined, None is returned.''' - return self.match_equal(schema, attribute_value, assertion_value) + def match_extensible(self, attribute_values, assertion_value): + return self.definition.match_equal(self.schema, attribute_values, assertion_value) - def match_less(self, schema, attribute_value, assertion_value): - '''Return whether attribute value is less than assertion values + def match_equal(self, attribute_values, assertion_value): + return self.definition.match_equal(self.schema, attribute_values, assertion_value) - Only available for ORDERING matching rules. + def match_approx(self, attribute_values, assertion_value): + return self.definition.match_approx(self.schema, attribute_values, assertion_value) - :returns: True if attribute value matches the assertion value, False if it - does not match. If the result is Undefined, None is returned.''' - return None +class OrderingMatchingRule(MatchingRule): + def __repr__(self): + return f'<ldapserver.schema.OrderingMatchingRule {self.ref}>' - def match_greater_or_equal(self, schema, attribute_value, assertion_value): - '''Return whether attribute value is greater than or equal to assertion values + def match_less(self, attribute_values, assertion_value): + return self.definition.match_less(self.schema, attribute_values, assertion_value) - Only available for ORDERING matching rules. + def match_greater_or_equal(self, attribute_values, assertion_value): + return self.definition.match_greater_or_equal(self.schema, attribute_values, assertion_value) - :returns: True if attribute value matches the assertion value, False if it - does not match. If the result is Undefined, None is returned.''' - return None +class SubstrMatchingRule(MatchingRule): + def __repr__(self): + return f'<ldapserver.schema.SubstrMatchingRule {self.ref}>' - def match_substr(self, schema, attribute_value, inital_substring, any_substrings, final_substring): - '''Return whether attribute value matches substring assertion + def match_extensible(self, attribute_values, assertion_value): + return self.definition.match_substr(self.schema, attribute_values, assertion_value[0], assertion_value[1], assertion_value[2]) - Only available for SUBSTR matching rules. + def match_substr(self, attribute_values, inital_substring, any_substrings, final_substring): + return self.definition.match_substr(self.schema, attribute_values, inital_substring, any_substrings, final_substring) - The type of `inital_substring`, `any_substrings` and `final_substring` - depends on the syntax of the attribute's equality matching rule! +class AttributeType: + def __init__(self, schema, definition): + self.schema = schema + self.definition = definition + self.oid = definition.oid + self.names = definition.name or [] + self.ref = self.names[0] if self.names else self.oid + self.sup = schema.attribute_types[definition.sup] if definition.sup else None + self.subtypes = set() + sup = self.sup + while sup: + self.sup.subtypes.add(self) + sup = sup.sup + self.equality = schema.matching_rules[definition.equality] if definition.equality else None + self.ordering = schema.matching_rules[definition.ordering] if definition.ordering else None + self.substr = schema.matching_rules[definition.substr] if definition.substr else None + if self.sup: + self.equality = self.equality or self.sup.equality + self.ordering = self.ordering or self.sup.ordering + self.substr = self.substr or self.sup.substr + self.syntax = schema.syntaxes[definition.syntax] if definition.syntax else self.sup.syntax + self.is_operational = (definition.usage != AttributeTypeUsage.userApplications) + schema._register(self, self.oid, self.ref, *self.names) + schema.attribute_types._register(self, self.oid, self.ref, *self.names) + if not self.is_operational: + schema.user_attribute_types.add(self) + self.compatible_matching_rules = self.syntax.compatible_matching_rules + for matching_rule in self.compatible_matching_rules: + matching_rule.compatible_attribute_types.add(self) - :param schema: Schema of the object whose attribute value is matched - :type schema: Schema - :param attribute_value: Attribute value (type according to attribute's syntax) - :type attribute_value: any + def __repr__(self): + return f'<ldapserver.schema.AttributeType {self.ref}>' + + def encode(self, value): + return self.syntax.encode(value) + + def decode(self, raw_value): + return self.syntax.decode(raw_value) + + def match_equal(self, attribute_values, assertion_value): + '''Return whether any attribute value equals assertion value + + :param attribute_values: Attribute values (type according to syntax) + :type attribute_values: List of any + :param assertion_value: Assertion value + :type assertion_value: bytes + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + if self.equality is None: + raise exceptions.LDAPInappropriateMatching() + assertion_value = self.equality.syntax.decode(assertion_value) + return self.equality.match_equal(attribute_values, assertion_value) + + def match_substr(self, attribute_values, inital_substring, any_substrings, final_substring): + '''Return whether any attribute value matches a substring assertion + + :param attribute_values: Attribute values (type according to syntax) + :type attribute_values: List of any :param inital_substring: Substring to match the beginning (optional) - :type inital_substring: any + :type inital_substring: bytes or None :param any_substrings: List of substrings to match between initial and final in order - :type any_substrings: list of any + :type any_substrings: list of bytes :param final_substring: Substring to match the end (optional) - :type final_substring: any - :returns: True if attribute value matches the assertion value, False if it - does not match. If the result is Undefined, None is returned.''' - return None - -class AttributeTypeUsage(enum.Enum): - '''Values for usage argument of `AttributeTypeUsage`''' - # pylint: disable=invalid-name - # user - userApplications = enum.auto() - # directory operational - directoryOperation = enum.auto() - # DSA-shared operational - distributedOperation = enum.auto() - # DSA-specific operational - dSAOperation = enum.auto() - -class AttributeType: - '''Represents an attribute type within a schema''' - # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-branches,too-many-statements - def __init__(self, oid, name=None, desc=None, obsolete=None, sup=None, - equality=None, ordering=None, substr=None, syntax=None, - single_value=None, collective=None, no_user_modification=None, - usage=None): - if sup is None and syntax is None: - raise ValueError('Either SUP or, syntax=syntax.must, be specified') - tokens = ['(', oid] - if name is not None: - tokens += ['NAME', "'"+escape(name)+"'"] # name is actually a list - if desc is not None: - tokens += ['DESC', "'"+escape(desc)+"'"] - if obsolete is not None: - tokens += ['OBSOLETE', obsolete] - if sup is not None: - tokens += ['SUP', sup.oid] - if equality is not None: - tokens += ['EQUALITY', equality.oid] - if ordering is not None: - tokens += ['ORDERING', ordering.oid] - if substr is not None: - tokens += ['SUBSTR', substr.oid] - if syntax is not None: - tokens += ['SYNTAX', syntax.ref] - if single_value is not None: - tokens += ['SINGLE-VALUE'] - if collective is not None: - tokens += ['COLLECTIVE'] - if no_user_modification is not None: - tokens += ['NO-USER-MODIFICATION'] - if usage is not None: - tokens += ['USAGE', usage.name] - tokens += [')'] - self.schema_encoding = ' '.join(tokens) - self.oid = oid - self.name = name - self.names = [name] if name is not None else [] - self.inherited_names = set(self.names) - self.obsolete = obsolete or False - self.sup = sup - if self.sup is not None: - self.inherited_names |= self.sup.inherited_names - self.equality = equality - if self.equality is None and self.sup is not None: - self.equality = self.sup.equality - self.ordering = ordering - if self.ordering is None and self.sup is not None: - self.ordering = self.sup.ordering - self.substr = substr - if self.substr is None and self.sup is not None: - self.substr = self.sup.substr - self.syntax = syntax - if self.syntax is None and self.sup is not None: - self.syntax = self.sup.syntax - self.single_value = single_value or False - self.collective = collective or False - self.no_user_modification = no_user_modification or False - self.usage = usage or AttributeTypeUsage.userApplications - - def to_definition(self): - '''Get string reperesentation as used in attributeTypes attribute''' - return self.schema_encoding - - def get_first_component_oid(self): - '''Used by objectIdentifierFirstComponentMatch''' - return self.oid - - def __repr__(self): - return f'<ldapserver.schema.AttributeType {self.oid}>' - -class ObjectClassKind(enum.Enum): - '''Values for kind argument of `ObjectClass`''' - ABSTRACT = enum.auto() - STRUCTURAL = enum.auto() - AUXILIARY = enum.auto() + :type final_substring: bytes or None + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + if self.equality is None or self.substr is None: + raise exceptions.LDAPInappropriateMatching() + if inital_substring: + inital_substring = self.equality.syntax.decode(inital_substring) + any_substrings = [self.equality.syntax.decode(substring) for substring in any_substrings] + if final_substring: + final_substring = self.equality.syntax.decode(final_substring) + return self.substr.match_substr(attribute_values, inital_substring, any_substrings, final_substring) + + def match_approx(self, attribute_values, assertion_value): + '''Return whether any attribute value approximatly equals assertion value + + :param attribute_values: Attribute values (type according to syntax) + :type attribute_values: List of any + :param assertion_value: Assertion value + :type assertion_value: bytes + :returns: True if any attribute values matches, False otherwise + :rtype: bool + :raises exceptions.LDAPError: if the result is undefined''' + if self.equality is None: + raise exceptions.LDAPInappropriateMatching() + assertion_value = self.equality.syntax.decode(assertion_value) + return self.equality.match_approx(attribute_values, assertion_value) + + def match_greater_or_equal(self, attribute_values, assertion_value): + if self.ordering is None: + raise exceptions.LDAPInappropriateMatching() + assertion_value = self.ordering.syntax.decode(assertion_value) + return self.ordering.match_greater_or_equal(attribute_values, assertion_value) + + def __match_less(self, attribute_values, assertion_value): + if self.ordering is None: + raise exceptions.LDAPInappropriateMatching() + assertion_value = self.ordering.syntax.decode(assertion_value) + return self.ordering.match_less(attribute_values, assertion_value) + + def match_less_or_equal(self, attribute_values, assertion_value): + equal_exc = None + try: + if self.match_equal(attribute_values, assertion_value): + return True + except exceptions.LDAPError as exc: + equal_exc = exc + if self.__match_less(attribute_values, assertion_value): + return True + if equal_exc is not None: + raise equal_exc + return False + + def match_extensible(self, attribute_values, assertion_value, matching_rule=None): + if not matching_rule: + matching_rule = self.equality + if not matching_rule or matching_rule not in self.compatible_matching_rules: + raise exceptions.LDAPInappropriateMatching() + assertion_value = matching_rule.syntax.decode(assertion_value) + return matching_rule.match_extensible(attribute_values, assertion_value) class ObjectClass: '''Representation of an object class wihin a schema''' - # pylint: disable=too-many-arguments - def __init__(self, oid, name=None, desc=None, obsolete=None, sup=None, - kind=None, must=None, may=None): - tokens = ['(', oid] - if name is not None: - tokens += ['NAME', "'"+escape(name)+"'"] # name is actually a list - if desc is not None: - tokens += ['DESC', "'"+escape(desc)+"'"] - if obsolete is not None: - tokens += ['OBSOLETE', obsolete] - if sup is not None: - tokens += ['SUP', sup.name] - if kind is not None: - tokens += [kind.name] - if must and len(must) == 1: - tokens += ['MUST', must[0].name] - elif must and len(must) > 1: - tokens += ['MUST', '('] - for index, attr in enumerate(must): - if index > 0: - tokens += ['$'] - tokens += [attr.name] - tokens += [')'] - if may and len(may) == 1: - tokens += ['MAY', may[0].name] - elif may and len(may) > 1: - tokens += ['MAY', '('] - for index, attr in enumerate(may): - if index > 0: - tokens += ['$'] - tokens += [attr.name] - tokens += [')'] - tokens += [')'] - self.schema_encoding = ' '.join(tokens) - self.oid = oid - self.name = name - self.names = [name] - self.desc = desc - self.obsolete = obsolete or False - self.sup = sup - self.kind = kind or ObjectClassKind.STRUCTURAL - self.must = must or [] - self.may = may or [] - - def to_definition(self): - '''Get string reperesentation as used in objectClasses attribute''' - return self.schema_encoding - - def get_first_component_oid(self): - '''Used by objectIdentifierFirstComponentMatch''' - return self.oid + def __init__(self, schema, definition): + self.schema = schema + self.definition = definition + self.oid = definition.oid + self.names = definition.name + self.ref = self.names[0] if self.names else self.oid + # Lookup dependencies to ensure consistency + # pylint: disable=pointless-statement + for sup_oid in definition.sup: + schema.object_classes[sup_oid] + for must_oid in definition.must: + schema.attribute_types[must_oid] + for may_oid in definition.may: + schema.attribute_types[may_oid] + schema._register(self, self.oid, self.ref, *self.names) + schema.object_classes._register(self, self.oid, self.ref, *self.names) def __repr__(self): - return f'<ldapserver.schema.ObjectClass {self.schema_encoding}>' - -DOTTED_DECIMAL_RE = re.compile(r'[0-9]+(\.[0-9]+)*') + return f'<ldapserver.schema.ObjectClass {self.ref}>' -def normalize_oid(oid): - return oid.lower().strip() +class OIDDict(collections.abc.Mapping): + def __init__(self): + self.__data = {} + self.__refs = [] + self.__numeric_oid_map = {} -class Schema: + def _register(self, obj, oid, ref, *names): + if obj in self.__data: + return + if oid in self.__data: + raise Exception(f'OID "{oid}" already registered') + for name in (ref,) + names: + if name.lower().strip() in self.__data: + raise Exception(f'Short descriptive name "{name}" already registered') + self.__refs.append(ref) + self.__data[obj] = obj + self.__numeric_oid_map[obj] = oid + for name in (oid, ref) + names: + self.__data[name.lower().strip()] = obj + self.__numeric_oid_map[name.lower().strip()] = oid + + def __getitem__(self, key): + if isinstance(key, str): + key = key.lower().strip() + return self.__data[key] + + def __iter__(self): + return iter(self.__refs) + + def __len__(self): + return len(self.__refs) + + def get_numeric_oid(self, key, default=None): + if isinstance(key, str): + key = key.lower().strip() + return self.__numeric_oid_map.get(key, default) + +class Schema(OIDDict): '''Collection of LDAP syntaxes, matching rules, attribute types and object classes forming an LDAP schema.''' - def __init__(self, object_classes=None, attribute_types=None, matching_rules=None, syntaxes=None): - self.syntaxes = [] - self.matching_rules = [] - self.attribute_types = [] - self.user_attribute_types = [] - self.object_classes = [] - self.__attribute_type_by_oid = {} - self.__attribute_type_and_subtypes_by_oid = {} - self.__oid_names = {} - for syntax in syntaxes: - self.register_syntax(syntax) - for matching_rule in matching_rules: - self.register_matching_rule(matching_rule) - for attribute_type in attribute_types: - self.register_attribute_type(attribute_type) - for object_class in object_classes: - self.register_object_class(object_class) - - def extend(self, *schemas, object_classes=None, attribute_types=None, matching_rules=None, syntaxes=None): - '''Return new Schema instance with all object classes, attribute types, - matching rules and syntaxes from this schema and additional ones. - - :return: Schema object with all schema data combined - :rtype: Schema''' - object_classes = self.object_classes + (object_classes or []) - attribute_types = self.attribute_types + (attribute_types or []) - matching_rules = self.matching_rules + (matching_rules or []) - syntaxes = self.syntaxes + (syntaxes or []) - for schema in schemas: - object_classes += schema.object_classes - attribute_types += schema.attribute_types - matching_rules += schema.matching_rules - syntaxes += schema.syntaxes - return Schema(object_classes, attribute_types, matching_rules, syntaxes) - - def register_syntax(self, syntax): - '''Add syntax to schema - - :param syntax: Syntax (subclass, not instance!) to add - :type syntax: Syntax subclass''' - if syntax in self.syntaxes: - return - self.register_oid(syntax.oid) - self.syntaxes.append(syntax) - - def register_matching_rule(self, matching_rule): - '''Add matching rule and the referenced syntax to schema - - :param matching_rule: Matching rule to add - :type matching_rule: MatchingRule''' - if matching_rule in self.matching_rules: - return - self.register_syntax(type(matching_rule.syntax)) - self.register_oid(matching_rule.oid, *matching_rule.names) - self.matching_rules.append(matching_rule) - - def register_attribute_type(self, attribute_type): - '''Add attribute type and all referenced matching rules and syntaxes to schema - - :param attribute_type: Attribute type to add - :type attribute_type: AttributeType''' - if attribute_type in self.attribute_types: - return - self.register_syntax(type(attribute_type.syntax)) - if attribute_type.equality: - self.register_matching_rule(attribute_type.equality) - if attribute_type.ordering: - self.register_matching_rule(attribute_type.ordering) - if attribute_type.substr: - self.register_matching_rule(attribute_type.substr) - self.register_oid(attribute_type.oid, *attribute_type.names) - for oid in [attribute_type.oid] + attribute_type.names: - self.__attribute_type_by_oid[normalize_oid(oid)] = attribute_type - self.__attribute_type_and_subtypes_by_oid.setdefault(normalize_oid(oid), []) - self.__attribute_type_and_subtypes_by_oid[normalize_oid(oid)].append(attribute_type) - for oid in attribute_type.inherited_names: - self.__attribute_type_and_subtypes_by_oid.setdefault(normalize_oid(oid), []) - self.__attribute_type_and_subtypes_by_oid[normalize_oid(oid)].append(attribute_type) - self.attribute_types.append(attribute_type) - if attribute_type.usage == AttributeTypeUsage.userApplications: - self.user_attribute_types.append(attribute_type) - - def register_object_class(self, object_class): - '''Add object class and all referenced attribute types, matching rules and syntaxes to schema - - :param object_class: Object class to add - :type object_class: ObjectClass''' - if object_class in self.object_classes: - return - for attribute_type in object_class.may + object_class.must: - self.register_attribute_type(attribute_type) - self.register_oid(object_class.oid, *object_class.names) - self.object_classes.append(object_class) - - def get_attribute_type(self, oid): - '''Get attribute type by its OID - - :param oid: Numeric OID or short descriptive name of attribute type - :type oid: str - :return: Attribute type identified by OID - :rtype: AttributeType - :raises KeyError: if attribute type is not found''' - attribute_type = self.__attribute_type_by_oid.get(normalize_oid(oid)) - if attribute_type is None: - raise KeyError(f'Attribute "{oid}" not in schema') - return attribute_type - - def get_attribute_type_and_subtypes(self, oid): - '''Get attribute type by its OID with all subtypes (if any) - - :param oid: Numeric OID or short descriptive name of attribute type - :type oid: str - :return: List containing the attribute type and its subtypes or empty list - if attribute type is not found - :rtype: List[AttributeType]''' - return self.__attribute_type_and_subtypes_by_oid.get(normalize_oid(oid), []) - - def register_oid(self, numeric_oid, *names): - '''Register numeric OID and optionally short descriptive names with the schema - - Both the numeric OID and all names must be unique within the schema. - - :param numeric_oid: Numeric OID - :type numeric_oid: str - :param names: Short descriptive names for OID - :type names: str''' - for name in names: - if self.__oid_names.get(normalize_oid(name), numeric_oid) != numeric_oid: - raise Exception(f'OID short descriptive name "{name}" is already used in schema') - for name in names: - self.__oid_names[normalize_oid(name)] = numeric_oid - - def get_numeric_oid(self, oid): - '''Return numeric OID for a given OID short descriptive name - - If `oid` is a numeric OID it is returned normalized. - - :param oid: OID short descriptive name or numeric OID - :type oid: str - :return: Numeric OID in dotted-decimal form or None if `oid` is not - recognized and is not a numeric OID. - :rtype: str or None''' - oid = normalize_oid(oid) - if DOTTED_DECIMAL_RE.fullmatch(oid): - return oid - return self.__oid_names.get(oid) + # pylint: disable=too-many-branches,too-many-statements + def __init__(self, object_class_definitions=None, attribute_type_definitions=None, + matching_rule_definitions=None, syntax_definitions=None): + super().__init__() + syntaxes_by_tag = {} + + # Add syntaxes + self.syntaxes = OIDDict() + for definition in syntax_definitions or []: + if definition.oid not in self.syntaxes: + Syntax(self, definition, syntaxes_by_tag) + self.syntax_definitions = [syntax.definition for syntax in self.syntaxes.values()] + + # Add matching rules + self.matching_rules = OIDDict() + for definition in matching_rule_definitions or []: + if definition.kind == MatchingRuleKind.EQUALITY: + cls = EqualityMatchingRule + elif definition.kind == MatchingRuleKind.ORDERING: + cls = OrderingMatchingRule + elif definition.kind == MatchingRuleKind.SUBSTR: + cls = SubstrMatchingRule + else: + raise ValueError('Invalid matching rule kind') + if definition.oid not in self.matching_rules: + cls(self, definition, syntaxes_by_tag) + self.matching_rule_definitions = [matching_rule.definition for matching_rule in self.matching_rules.values()] + + # Add attribute types + attribute_type_definitions = [AttributeTypeDefinition.from_str(item) + if isinstance(item, str) else item + for item in attribute_type_definitions or []] + self.attribute_types = OIDDict() + self.user_attribute_types = set() + # Attribute types may refer to other (superior) attribute types. To resolve + # these dependencies we cycle through the definitions, each time adding + # those not added yet with fulfilled dependencies. Finally we add all the + # remaining ones to provoke exceptions. + keep_running = True + while keep_running: + keep_running = False + for definition in attribute_type_definitions: + if definition.oid in self.attribute_types: + continue + if definition.sup and definition.sup not in self.attribute_types: + continue + AttributeType(self, definition) + keep_running = True + for definition in attribute_type_definitions: + if definition.oid not in self.attribute_types: + AttributeType(self, definition) + self.attribute_type_definitions = [attribute_type.definition for attribute_type in self.attribute_types.values()] + + # Add object classes + object_class_definitions = [ObjectClassDefinition.from_str(item) + if isinstance(item, str) else item + for item in object_class_definitions or []] + self.object_classes = OIDDict() + # Object classes may refer to other (superior) object classes. To resolve + # these dependencies we cycle through the definitions, each time adding + # those not added yet with fulfilled dependencies. Finally we add all the + # remaining ones to provoke exceptions. + keep_running = True + while keep_running: + keep_running = False + for definition in object_class_definitions: + if definition.oid in self.object_classes: + continue + if any(map(lambda oid: oid and oid not in self.object_classes, definition.sup)): + continue + ObjectClass(self, definition) + keep_running = True + for definition in object_class_definitions: + if definition.oid not in self.object_classes: + ObjectClass(self, definition) + self.object_class_definitions = [object_class.definition for object_class in self.object_classes.values()] + + # Generate and add matching rules + self.matching_rule_use_definitions = [] + for matching_rule in self.matching_rules.values(): + definition = matching_rule.definition + applies = [attribute_type.ref for attribute_type in matching_rule.compatible_attribute_types] + rule_use = MatchingRuleUseDefinition(definition.oid, name=definition.name, + desc=definition.desc, obsolete=definition.obsolete, applies=applies, + extensions=definition.extensions) + if applies: + self.matching_rule_use_definitions.append(rule_use) + + def extend(self, object_class_definitions=None, attribute_type_definitions=None, + matching_rule_definitions=None, syntax_definitions=None): + syntax_definitions = self.syntax_definitions + (syntax_definitions or []) + matching_rule_definitions = self.matching_rule_definitions + (matching_rule_definitions or []) + attribute_type_definitions = self.attribute_type_definitions + (attribute_type_definitions or []) + object_class_definitions = self.object_class_definitions + (object_class_definitions or []) + return type(self)(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions, + object_class_definitions=object_class_definitions) + + def __or__(self, value): + return self.extend(syntax_definitions=value.syntax_definitions, + matching_rule_definitions=value.matching_rule_definitions, + attribute_type_definitions=value.attribute_type_definitions, + object_class_definitions=value.object_class_definitions) diff --git a/ldapserver/server.py b/ldapserver/server.py index 4c1235bc2455d5cbee0fc25a52bf95a57c31c021..d653f716cdd6deb6068e5750018c30e67b34e87a 100644 --- a/ldapserver/server.py +++ b/ldapserver/server.py @@ -9,7 +9,6 @@ import string import itertools from . import asn1, exceptions, ldap, schema, objects -from .dn import DN def pop_control(controls, oid): result = None @@ -173,7 +172,7 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): mechansims. Attributes can be accessed in a dict-like fashion. ''' - subschema = objects.SubschemaSubentry(schema.RFC4519_SUBSCHEMA, 'cn=Subschema') + subschema = objects.SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema', cn=['Subschema']) ''' .. py:attribute:: subschema @@ -190,60 +189,25 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): super().setup() self.rootdse = self.subschema.RootDSE() self.rootdse['objectClass'] = ['top'] - self.rootdse['supportedSASLMechanisms'] = self.get_sasl_mechanisms - self.rootdse['supportedExtension'] = self.get_extentions - self.rootdse['supportedControl'] = self.get_controls - self.rootdse['supportedLDAPVersion'] = ['3'] - self.bind_object = None - self.bind_sasl_state = None - self.__paged_searches = {} # pagination cookie -> (iterator, orig_op) - self.__paged_cookie_counter = 0 - - def get_extentions(self): - '''Get supported LDAP extentions - - :returns: OIDs of supported LDAP extentions - :rtype: list of strings - - Called whenever the root DSE attribute "supportedExtension" is queried.''' - res = [] if self.supports_starttls: - res.append(ldap.STARTTLS_OID) + self.rootdse['supportedExtension'].append(ldap.STARTTLS_OID) if self.supports_whoami: - res.append(ldap.WHOAMI_OID) + self.rootdse['supportedExtension'].append(ldap.WHOAMI_OID) if self.supports_password_modify: - res.append(ldap.PASSWORD_MODIFY_OID) - return res - - def get_controls(self): - '''Get supported LDAP controls - - :returns: OIDs of supported LDAP controls - :rtype: list of strings - - Called whenever the root DSE attribute "supportedControl" is queried.''' - res = [] + self.rootdse['supportedExtension'].append(ldap.PASSWORD_MODIFY_OID) if self.supports_paged_results: - res.append(ldap.PAGED_RESULTS_OID) - return res - - def get_sasl_mechanisms(self): - '''Get supported SASL mechanisms - - :returns: Names of supported SASL mechanisms - :rtype: list of strings - - SASL mechanism name are typically all-caps, like "EXTERNAL". - - Called whenever the root DSE attribute "supportedSASLMechanisms" is queried.''' - res = [] + self.rootdse['supportedControl'].append(ldap.PAGED_RESULTS_OID) if self.supports_sasl_anonymous: - res.append('ANONYMOUS') + self.rootdse['supportedSASLMechanisms'].append('ANONYMOUS') if self.supports_sasl_plain: - res.append('PLAIN') + self.rootdse['supportedSASLMechanisms'].append('PLAIN') if self.supports_sasl_external: - res.append('EXTERNAL') - return res + self.rootdse['supportedSASLMechanisms'].append('EXTERNAL') + self.rootdse['supportedLDAPVersion'] = ['3'] + self.bind_object = None + self.bind_sasl_state = None + self.__paged_searches = {} # pagination cookie -> (iterator, orig_op) + self.__paged_cookie_counter = 0 def handle_bind(self, op, controls=None): reject_critical_controls(controls) @@ -468,7 +432,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): paged_control = ldap.PagedResultsValue.from_ber(paged_control.controlValue)[0] if not paged_control.cookie: # New paged search request results = self.do_search(op.baseObject, op.scope, op.filter) - results = filter(lambda obj: obj.match_search(op.baseObject, op.scope, op.filter), results) + results = map(lambda obj: obj.search(op.baseObject, op.scope, op.filter, op.attributes, op.typesOnly), results) + results = filter(None, results) iterator = iter(mark_last(results)) else: # Continue existing paged search try: @@ -483,10 +448,10 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): is_last = True entries = 0 time_start = time.perf_counter() - for obj, is_last in itertools.islice(iterator, 0, paged_control.size): - self.logger.debug('SEARCH entry %r', obj) - yield obj.get_search_result_entry(op.attributes, op.typesOnly) + for entry, is_last in itertools.islice(iterator, 0, paged_control.size): + self.logger.debug('SEARCH entry %r', entry) entries += 1 + yield entry cookie = b'' if not is_last: cookie = str(self.__paged_cookie_counter).encode() @@ -510,8 +475,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): entries = 0 time_start = time.perf_counter() for obj in self.do_search(op.baseObject, op.scope, op.filter): - if obj.match_search(op.baseObject, op.scope, op.filter): - entry = obj.get_search_result_entry(op.attributes, op.typesOnly) + entry = obj.search(op.baseObject, op.scope, op.filter, op.attributes, op.typesOnly) + if entry: self.logger.debug('SEARCH entry %r', entry) entries += 1 yield entry @@ -569,11 +534,15 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): The default implementation calls `do_search` and returns the first object with the right DN.''' objs = self.do_search(dn, ldap.SearchScope.baseObject, ldap.FilterPresent(attribute='objectClass')) - dn = DN.from_str(dn) for obj in objs: - if obj.dn == dn: + try: + obj.compare(dn) return obj - return None + except exceptions.LDAPNoSuchObject: + pass + except exceptions.LDAPError: + return obj + raise exceptions.LDAPNoSuchObject() def handle_unbind(self, op, controls=None): self.logger.info('UNBIND') @@ -592,6 +561,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): except Exception: # pylint: disable=broad-except traceback.print_exc() self.keep_running = False + if ldap.STARTTLS_OID in self.rootdse['supportedExtension']: + self.rootdse['supportedExtension'].remove(ldap.STARTTLS_OID) elif op.requestName == ldap.WHOAMI_OID and self.supports_whoami: self.logger.info('EXTENDED WHOAMI') # "Who am I?" Operation (RFC 4532) diff --git a/tests/test_objects.py b/tests/test_objects.py new file mode 100644 index 0000000000000000000000000000000000000000..dae7801c0c7ebdd0cb13449e16e45f0200e2af3d --- /dev/null +++ b/tests/test_objects.py @@ -0,0 +1,772 @@ +import unittest +import datetime + +import ldapserver +from ldapserver.objects import AttributeDict, Object, RootDSE, SubschemaSubentry, ObjectTemplate, WILDCARD_VALUE +from ldapserver.dn import DN +from ldapserver import ldap + +schema = ldapserver.schema.RFC4519_SCHEMA + +class TestAttributeDict(unittest.TestCase): + def test_init(self): + AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + + def test_getitem(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + self.assertEqual(attrs['cn'], ['foo', 'bar']) + self.assertEqual(attrs['CN'], ['foo', 'bar']) + self.assertEqual(attrs['2.5.4.3'], ['foo', 'bar']) + self.assertEqual(attrs[schema['cn']], ['foo', 'bar']) + self.assertEqual(attrs['uid'], []) + self.assertEqual(attrs['name'], []) + self.assertEqual(attrs['objectClass'], []) + attrs['objectClass'].append('top') + self.assertEqual(attrs['objectClass'], ['top']) + with self.assertRaises(KeyError): + attrs['foobar'] + + def test_get(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + self.assertEqual(attrs.get('cn'), ['foo', 'bar']) + self.assertEqual(attrs.get('CN'), ['foo', 'bar']) + self.assertEqual(attrs.get('2.5.4.3'), ['foo', 'bar']) + self.assertEqual(attrs.get(schema['cn']), ['foo', 'bar']) + self.assertEqual(attrs.get('uid'), []) + self.assertEqual(attrs.get('uid', ['default']), ['default']) + self.assertEqual(attrs.get('name'), []) + self.assertEqual(attrs.get('name', ['default']), ['default']) + self.assertEqual(attrs.get('name', subtypes=True), ['foo', 'bar']) + self.assertEqual(attrs.get('foobar'), []) + + def test_contains(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + self.assertIn('cn', attrs) + self.assertNotIn('uid', attrs) + self.assertNotIn('objectClass', attrs) + attrs['objectClass'].append('top') + self.assertIn('objectClass', attrs) + self.assertNotIn('foobar', attrs) + + def test_setitem(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + attrs['cn'] = ['bar', 'foo'] + self.assertEqual(attrs['cn'], ['bar', 'foo']) + attrs['cn'] = [] + self.assertEqual(attrs['cn'], []) + attrs['objectClass'] = [] + self.assertEqual(attrs['objectClass'], []) + with self.assertRaises(KeyError): + attrs['foobar'] = ['test'] + + def test_delitem(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + del attrs['cn'] + self.assertEqual(attrs['cn'], []) + del attrs['cn'] # does nothing + with self.assertRaises(KeyError): + del attrs['foobar'] + + def test_iter(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + self.assertEqual(list(attrs), ['cn']) + + def test_len(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[]) + self.assertEqual(len(attrs), 1) + attrs['objectClass'] = ['top'] + self.assertEqual(len(attrs), 2) + + def test_keys(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[], objectclass=['top']) + self.assertEqual(set(attrs.keys()), {'cn', 'objectClass'}) + self.assertEqual(set(attrs.keys(types=True)), {schema['cn'], schema['objectClass']}) + + def test_items(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[], objectclass=['top']) + self.assertEqual(list(sorted(attrs.items())), [('cn', ['foo', 'bar']), ('objectClass', ['top'])]) + self.assertIn((schema['cn'], ['foo', 'bar']), attrs.items(types=True)) + self.assertIn((schema['objectClass'], ['top']), attrs.items(types=True)) + + def test_setdefault(self): + attrs = AttributeDict(schema, cn=['foo', 'bar'], uid=[], objectclass=['top']) + self.assertEqual(attrs.setdefault('CN', ['default']), ['foo', 'bar']) + self.assertEqual(attrs['Cn'], ['foo', 'bar']) + self.assertEqual(attrs.setdefault('c', ['default']), ['default']) + self.assertEqual(attrs['c'], ['default']) + +class TestObject(unittest.TestCase): + def test_init(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[]) + self.assertEqual(obj.dn, DN.from_str('cn=foo,dc=example,dc=com')) + self.assertEqual(obj['cn'], ['foo', 'bar']) + self.assertEqual(obj['uid'], []) + + def test_match_search_dn(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', objectclass=['top']) + true_filter = ldap.FilterPresent('objectClass') + + scope = ldap.SearchScope.baseObject + self.assertTrue(obj.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('cn=bar,dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('', scope, true_filter)) + self.assertFalse(obj.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + scope = ldap.SearchScope.singleLevel + self.assertFalse(obj.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('cn=bar,dc=example,dc=com', scope, true_filter)) + self.assertTrue(obj.match_search('dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('', scope, true_filter)) + self.assertFalse(obj.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + scope = ldap.SearchScope.wholeSubtree + self.assertTrue(obj.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertFalse(obj.match_search('cn=bar,dc=example,dc=com', scope, true_filter)) + self.assertTrue(obj.match_search('dc=example,dc=com', scope, true_filter)) + self.assertTrue(obj.match_search('', scope, true_filter)) + self.assertFalse(obj.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + def test_match_search_filter_present(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + # True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterPresent('ObjectClass'))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterPresent('2.5.4.3'))) # OID + self.assertTrue(obj.match_search(dn, scope, ldap.FilterPresent('name'))) # subtype + # False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterPresent('uid'))) + # Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterPresent('undefined'))) + + def test_match_search_filter_not(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('ObjectClass')))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('2.5.4.3')))) # OID of cn + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('name')))) # subtype + # Not False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('uid')))) + # Not Undefined = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('undefined')))) + + def test_match_search_filter_and(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + true = ldap.FilterPresent('objectclass') + false = ldap.FilterPresent('uid') + undefined = ldap.FilterPresent('undefined') + # True = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterAnd([true]))) + # True and True = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterAnd([true, true]))) + # True and False = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterAnd([true, false]))) + # False and False = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterAnd([false, false]))) + # False and Undefined = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterAnd([false, undefined]))) + # True and Undefined = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterAnd([true, undefined]))) + + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true])))) + # Not (True and True) = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, true])))) + # Not (True and False) = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, false])))) + # Not (False and False) = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([false, false])))) + # Not (False and Undefined) = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([false, undefined])))) + # Not (True and Undefined) = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, undefined])))) + + def test_match_search_filter_or(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + true = ldap.FilterPresent('objectclass') + false = ldap.FilterPresent('uid') + undefined = ldap.FilterPresent('undefined') + # True = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterOr([true]))) + # True or True = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterOr([true, true]))) + # True or False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterOr([true, false]))) + # False or False = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterOr([false, false]))) + # True or Undefined = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterOr([true, undefined]))) + # False or Undefined = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterOr([false, undefined]))) + + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true])))) + # Not (True or True) = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, true])))) + # Not (True or False) = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, false])))) + # Not (False or False) = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, false])))) + # Not (True or Undefined) = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, undefined])))) + # Not (False or Undefined) = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, undefined])))) + + def test_match_search_filter_equal(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + # True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterEqual('ObjectClass', b'top'))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterEqual('2.5.4.3', b'Foo'))) # OID + self.assertTrue(obj.match_search(dn, scope, ldap.FilterEqual('name', b'bar'))) # subtype + # False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterEqual('ObjectClass', b'Person'))) + # Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterEqual('undefined', b'foo'))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterEqual('telexNumber', b'foo'))) # no EQUALITY + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('ObjectClass', b'top')))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('2.5.4.3', b'Foo')))) # OID + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('name', b'bar')))) # subtype + # Not False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('ObjectClass', b'Person')))) + # Not Undefined = Undefined (behaves like False) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('undefined', b'foo')))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('telexNumber', b'foo')))) # no EQUALITY + + def test_match_search_filter_substr(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foobar', 'test'], uid=[], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + self.assertTrue(obj.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo')]))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'bar')]))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo'), ldap.AnySubstring(b'b'), ldap.AnySubstring(b'a'), ldap.FinalSubstring(b'r')]))) + + def test_match_search_filter_le(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)]) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + self.assertTrue(obj.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161032Z'))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161033Z'))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161031Z'))) + # LessOrEqual is hybrid between EQUALITY and ORDERING + self.assertTrue(obj.match_search(dn, scope, ldap.FilterLessOrEqual('cn', b'foo'))) + + def test_match_search_filter_ge(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)]) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + self.assertTrue(obj.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161032Z'))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161033Z'))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161031Z'))) + # GreaterOrEqual is only ORDERING (which cn does not have) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterGreaterOrEqual('cn', b'foo'))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('cn', b'foo')))) + + def test_match_search_filter_extensible_attribute_type(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + # True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch(None, 'uid', b'Foobar', False))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'Foobar', False))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreSubstringsMatch', 'uid', b'F*b*r', False))) + # False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseExactMatch', 'uid', b'Foobar', False))) + # Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseExactMatch', 'createTimestamp', b'199412161032Z', False))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('generalizedTimeMatch', 'cn', b'199412161032Z', False))) + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch(None, 'uid', b'Foobar', False)))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'Foobar', False)))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreSubstringsMatch', 'uid', b'F*b*r', False)))) + # Not False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseExactMatch', 'uid', b'Foobar', False)))) + # Not Undefined = Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseExactMatch', 'createTimestamp', b'199412161032Z', False)))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'cn', b'199412161032Z', False)))) + + def test_match_search_filter_extensible_no_attribute_type(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + # True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foobar', False))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'top', False))) + # False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'person', False))) + # Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('octetStringOrderingMatch', None, b'someoctetstring', False))) + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foobar', False)))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'top', False)))) + # Not False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'person', False)))) + # Not Undefined = Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('octetStringOrderingMatch', None, b'someoctetstring', False)))) + + def test_match_search_filter_extensible_dn(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top']) + dn = 'cn=foo,dc=example,dc=com' + scope = ldap.SearchScope.baseObject + obj = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', True))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'foobar', True))) # also matches regular attributes + # False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', False))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'somethingelse', False))) + # Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False))) + # Not True = False + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', True)))) + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'foobar', True)))) # also matches regular attributes + # Not False = True + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', False)))) + self.assertTrue(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'somethingelse', False)))) + # Not Undefined = Undefined + self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False)))) + + def test_search(self): + class TrueObject(Object): + def match_search(self, base_obj, scope, filter_obj): + return True + + class FalseObject(Object): + def match_search(self, base_obj, scope, filter_obj): + return False + + obj = FalseObject(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN('cn=subschema')]) + self.assertIsNone(obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), [], False)) + obj = TrueObject(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN('cn=subschema')]) + result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), [], False) + self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com') + self.assertEqual(len(result.attributes), 2) + self.assertEqual({item.type: item.vals for item in result.attributes}, + {'cn': [b'foo', b'bar'], 'objectClass': [b'top']}) + result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), ['*'], False) + self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com') + self.assertEqual(len(result.attributes), 2) + self.assertEqual({item.type: item.vals for item in result.attributes}, + {'cn': [b'foo', b'bar'], 'objectClass': [b'top']}) + result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), ['1.1'], False) + self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com') + self.assertEqual(len(result.attributes), 0) + result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), ['cn', 'subschemaSubentry', 'foobar'], False) + self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com') + self.assertEqual(len(result.attributes), 2) + self.assertEqual({item.type: item.vals for item in result.attributes}, + {'cn': [b'foo', b'bar'], 'subschemaSubentry': [b'cn=subschema']}) + result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), ['cn', 'uid', 'subschemaSubentry', 'foobar'], True) + self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com') + self.assertEqual(len(result.attributes), 2) + self.assertEqual({item.type: item.vals for item in result.attributes}, + {'cn': [], 'subschemaSubentry': []}) + + def test_compare(self): + obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + self.assertTrue(obj.compare('cn=foo,dc=example,dc=com', 'cn', b'bar')) + self.assertFalse(obj.compare('cn=foo,dc=example,dc=com', 'cn', b'test')) + with self.assertRaises(ldapserver.exceptions.LDAPUndefinedAttributeType): + obj.compare('cn=foo,dc=example,dc=com', 'foobar', b'test') + with self.assertRaises(ldapserver.exceptions.LDAPNoSuchObject): + obj.compare('cn=bar,dc=example,dc=com', 'cn', b'test') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + obj.compare('cn=foo,dc=example,dc=com', 'objectclass', b'undefined') + +class TestRootDSE(unittest.TestCase): + def test_init(self): + obj = RootDSE(schema) + self.assertEqual(obj.dn, DN()) + obj = RootDSE(schema, cn=['foo', 'bar']) + self.assertEqual(obj.dn, DN()) + self.assertEqual(obj['cn'], ['foo', 'bar']) + + def test_match_search(self): + obj = RootDSE(schema, cn=['foo', 'bar'], objectclass=['top']) + self.assertTrue(obj.match_search('', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'))) + self.assertFalse(obj.match_search('cn=root', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'))) + self.assertFalse(obj.match_search('', ldap.SearchScope.singleLevel, ldap.FilterPresent('objectclass'))) + self.assertFalse(obj.match_search('', ldap.SearchScope.wholeSubtree, ldap.FilterPresent('objectclass'))) + self.assertFalse(obj.match_search('', ldap.SearchScope.baseObject, ldap.FilterPresent('cn'))) + +class TestObjectTemplate(unittest.TestCase): + def test_init(self): + obj = ObjectTemplate(schema, 'ou=users,dc=example,dc=com', 'uid', cn=['foo', 'bar'], uid=[]) + + def test_match_search_dn(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + true_filter = ldap.FilterPresent('objectClass') + + scope = ldap.SearchScope.baseObject + self.assertTrue(template.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertFalse(template.match_search('dc=example,dc=com', scope, true_filter)) + self.assertFalse(template.match_search('', scope, true_filter)) + self.assertFalse(template.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + scope = ldap.SearchScope.singleLevel + self.assertFalse(template.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertTrue(template.match_search('dc=example,dc=com', scope, true_filter)) + self.assertFalse(template.match_search('', scope, true_filter)) + self.assertFalse(template.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + scope = ldap.SearchScope.wholeSubtree + self.assertTrue(template.match_search('cn=foo,dc=example,dc=com', scope, true_filter)) + self.assertTrue(template.match_search('dc=example,dc=com', scope, true_filter)) + self.assertTrue(template.match_search('', scope, true_filter)) + self.assertFalse(template.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter)) + + def test_match_search_filter_present(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterPresent('objectclass'))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterPresent('uid'))) + # Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterPresent('undefined'))) + # Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterPresent('cn'))) + # We verify in ..._filter_not that Undefined/Maybe are not just False/True + + def test_match_search_filter_not(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('objectclass')))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('uid')))) + # Not Undefined = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('undefined')))) + # Not Maybe = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('cn')))) + + def test_match_search_filter_and(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + true = ldap.FilterPresent('objectclass') + false = ldap.FilterPresent('uid') + undefined = ldap.FilterPresent('undefined') + maybe = ldap.FilterPresent('cn') + + # True = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterAnd([true]))) + # True and True = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterAnd([true, true]))) + # True and False = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([true, false]))) + # False and False = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([false, false]))) + # False and Undefined = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([false, undefined]))) + # True and Undefined = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([true, undefined]))) + # False and Maybe = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([false, maybe]))) + # True and Maybe = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterAnd([true, maybe]))) + # Undefined and Maybe = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterAnd([undefined, maybe]))) + + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true])))) + # Not (True and True) = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, true])))) + # Not (True and False) = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, false])))) + # Not (False and False) = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([false, false])))) + # Not (False and Undefined) = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([false, undefined])))) + # Not (True and Undefined) = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, undefined])))) + # Not (False and Maybe) = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([false, maybe])))) + # Not (True and Maybe) = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, maybe])))) + # Not (Undefined and Maybe) = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([undefined, maybe])))) + + def test_match_search_filter_or(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + true = ldap.FilterPresent('objectclass') + false = ldap.FilterPresent('uid') + undefined = ldap.FilterPresent('undefined') + maybe = ldap.FilterPresent('cn') + + # True = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([true]))) + # True or True = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([true, true]))) + # True or False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([true, false]))) + # False or False = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterOr([false, false]))) + # True or Undefined = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([true, undefined]))) + # False or Undefined = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterOr([false, undefined]))) + # True or Maybe = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([true, maybe]))) + # False or Maybe = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([false, maybe]))) + # Undefined or Maybe = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterOr([undefined, maybe]))) + + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true])))) + # Not (True or True) = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, true])))) + # Not (True or False) = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, false])))) + # Not (False or False) = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, false])))) + # Not (True or Undefined) = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, undefined])))) + # Not (False or Undefined) = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, undefined])))) + # Not (True or Maybe) = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([true, maybe])))) + # Not (False or Maybe) = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, maybe])))) + # Not (Undefined or Maybe) = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([undefined, maybe])))) + + def test_match_search_filter_equal(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterEqual('ObjectClass', b'top'))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterEqual('ObjectClass', b'Person'))) + # Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterEqual('undefined', b'foo'))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterEqual('telexNumber', b'foo'))) # no EQUALITY + # Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterEqual('cn', b'foo'))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterEqual('2.5.4.3', b'Foo'))) # OID + self.assertTrue(template.match_search(dn, scope, ldap.FilterEqual('name', b'bar'))) # subtype + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('ObjectClass', b'top')))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('ObjectClass', b'Person')))) + # Not Undefined = Undefined (behaves like False) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('undefined', b'foo')))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('telexNumber', b'foo')))) # no EQUALITY + # Not Maybe = Maybe (behaves like True) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('cn', b'Foo')))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('2.5.4.3', b'Foo')))) # OID + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('name', b'bar')))) # subtype + + def test_match_search_filter_substr(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar', 'test']) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterSubstrings('uid', [ldap.InitialSubstring(b'foo')]))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterSubstrings('uid', [ldap.InitialSubstring(b'bar')]))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterSubstrings('objectclass', [ldap.InitialSubstring(b'foo')]))) + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo')]))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterSubstrings('uid', [ldap.InitialSubstring(b'foo')])))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterSubstrings('uid', [ldap.InitialSubstring(b'bar')])))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterSubstrings('objectclass', [ldap.InitialSubstring(b'foo')])))) + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo')])))) + + def test_match_search_filter_le(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161032Z'))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161033Z'))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterLessOrEqual('objectclass', b'top'))) # LessOrEqual is hybrid between EQUALITY and ORDERING + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161031Z'))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'invalid-date'))) + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterLessOrEqual('modifyTimestamp', b'199412161032Z'))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('createTimestamp', b'199412161032Z')))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('createTimestamp', b'199412161033Z')))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('objectclass', b'top')))) # LessOrEqual is hybrid between EQUALITY and ORDERING + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('createTimestamp', b'199412161031Z')))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('createTimestamp', b'invalid-date')))) + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('modifyTimestamp', b'199412161032Z')))) + + def test_match_search_filter_ge(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE]) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161032Z'))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161031Z'))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161033Z'))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'invalid-date'))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('objectclass', b'top'))) # GreaterOrEqual is only ORDERING + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterGreaterOrEqual('modifyTimestamp', b'199412161032Z'))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('createTimestamp', b'199412161032Z')))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('createTimestamp', b'199412161031Z')))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('createTimestamp', b'199412161033Z')))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('createTimestamp', b'invalid-date')))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('objectclass', b'top')))) # GreaterOrEqual is only ORDERING + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('modifyTimestamp', b'199412161032Z')))) + + def test_match_search_filter_extensible_attribute_type(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch(None, 'uid', b'Foobar', False))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'Foobar', False))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreSubstringsMatch', 'uid', b'F*b*r', False))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseExactMatch', 'uid', b'Foobar', False))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseExactMatch', 'createTimestamp', b'199412161032Z', False))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('generalizedTimeMatch', 'cn', b'199412161032Z', False))) + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'Foobar', False))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch(None, 'uid', b'Foobar', False)))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'Foobar', False)))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreSubstringsMatch', 'uid', b'F*b*r', False)))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseExactMatch', 'uid', b'Foobar', False)))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseExactMatch', 'createTimestamp', b'199412161032Z', False)))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'cn', b'199412161032Z', False)))) + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'Foobar', False)))) + + def test_match_search_filter_extensible_no_attribute_type(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foobar', False))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'top', False))) + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'person', False))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('octetStringOrderingMatch', None, b'someoctetstring', False))) + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foo', False))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foobar', False)))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'top', False)))) + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('objectIdentifierMatch', None, b'person', False)))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('octetStringOrderingMatch', None, b'someoctetstring', False)))) + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foo', False)))) + + def test_match_search_filter_extensible_dn(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) + dn = 'dc=example,dc=com' + scope = ldap.SearchScope.wholeSubtree + # True + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', True))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'foobar', True))) # also matches regular attributes + # False + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', False))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'somethingelse', False))) + # Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False))) + # Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'foo', True))) + # Not True = False + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', True)))) + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'uid', b'foobar', True)))) # also matches regular attributes + # Not False = True + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'example', False)))) + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'dc', b'somethingelse', False)))) + # Not Undefined = Undefined + self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False)))) + # Not Maybe = Maybe + self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'foo', True)))) + + def test_extract_search_constraints(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) + self.assertEqual(dict(template.extract_search_constraints('dc=exapmle,dc=com', ldap.SearchScope.wholeSubtree, ldap.FilterEqual('cn', b'foo')).items()), {'cn': ['foo']}) + self.assertEqual(dict(template.extract_search_constraints('dc=exapmle,dc=com', ldap.SearchScope.wholeSubtree, ldap.FilterAnd([ldap.FilterEqual('objectclass', b'top'), ldap.FilterEqual('cn', b'foo')])).items()), {'cn': ['foo'], 'objectClass': ['top']}) + self.assertEqual(dict(template.extract_search_constraints('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectClass')).items()), {'cn': ['foo']}) + self.assertEqual(dict(template.extract_search_constraints('dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectClass')).items()), {}) + + def test_create_object(self): + template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], c=[WILDCARD_VALUE], uid=['foobar']) + obj = template.create_object('foo', cn=['foo', 'bar'], c=['DE']) + self.assertEqual(obj.dn, DN('cn=foo,dc=example,dc=com')) + self.assertEqual(dict(obj.items()), {'cn': ['foo', 'bar'], 'uid': ['foobar'], 'c': ['DE'], 'objectClass': ['top']}) + obj = template.create_object('foo', cn=['foo', 'bar']) + self.assertEqual(dict(obj.items()), {'cn': ['foo', 'bar'], 'uid': ['foobar'], 'objectClass': ['top']}) + with self.assertRaises(ValueError): + template.create_object('foo', cn=['foo', 'bar'], c=['DE'], description=['foo bar']) + +class TestSubschemaSubentry(unittest.TestCase): + def test_init(self): + obj = SubschemaSubentry(schema, 'cn=Subschema', cn=['Subschema']) + self.assertIn('subschema', obj['objectClass']) + + def test_match_search(self): + obj = SubschemaSubentry(schema, 'cn=Subschema', cn=['Subschema']) + self.assertIn("( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", [str(item) for item in obj['objectClasses']]) + self.assertIn("( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' )", [str(item) for item in obj['ldapSyntaxes']]) + self.assertIn("( 2.5.13.5 NAME 'caseExactMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", [str(item) for item in obj['matchingRules']]) + self.assertIn("( 2.5.21.6 NAME 'objectClasses' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation )", [str(item) for item in obj['attributeTypes']]) + [str(item) for item in obj['matchingRuleUse']] + + def test_constructors(self): + subschema = SubschemaSubentry(schema, 'cn=Subschema', cn=['Subschema']) + attrs = subschema.AttributeDict(cn=['foo']) + self.assertIsInstance(attrs, AttributeDict) + self.assertIs(attrs.schema, subschema.schema) + self.assertEqual(attrs['cn'], ['foo']) + obj = subschema.Object('cn=foo,dc=example,dc=com', cn=['foo']) + self.assertIsInstance(obj, Object) + self.assertIs(obj.schema, subschema.schema) + self.assertEqual(obj.dn, DN('cn=foo,dc=example,dc=com')) + self.assertEqual(obj['cn'], ['foo']) + self.assertEqual(obj['subschemaSubentry'], [DN('cn=Subschema')]) + rootdse = subschema.RootDSE(cn=['foo']) + self.assertIsInstance(rootdse, RootDSE) + self.assertIs(rootdse.schema, subschema.schema) + self.assertEqual(rootdse['cn'], ['foo']) + template = subschema.ObjectTemplate('dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + self.assertIsInstance(template, ObjectTemplate) + self.assertIs(template.schema, subschema.schema) + self.assertEqual(template['subschemaSubentry'], [DN('cn=Subschema')]) diff --git a/tests/test_schema.py b/tests/test_schema.py new file mode 100644 index 0000000000000000000000000000000000000000..ce8d83fdc827a469aa30aa0f50769fcd0d8de029 --- /dev/null +++ b/tests/test_schema.py @@ -0,0 +1,399 @@ +import unittest +import datetime + +import ldapserver +from ldapserver.schema.types import OIDDict, Schema +from ldapserver.schema import syntaxes, matching_rules + +class TestOIDDict(unittest.TestCase): + def test_lookup(self): + class TestObj: + pass + oiddict = OIDDict() + obj1 = TestObj() + oiddict._register(obj1, '1.1.0', '1.1.0') + obj2 = TestObj() + oiddict._register(obj2, '1.1.1', 'fooBar', 'foo', 'bar') + obj3 = TestObj() + oiddict._register(obj3, '1.1.2', 'test', 'test') + self.assertIs(oiddict[obj1], obj1) + self.assertIs(oiddict['1.1.0'], obj1) + self.assertIs(oiddict[obj2], obj2) + self.assertIs(oiddict['1.1.1'], obj2) + self.assertIs(oiddict['fooBar'], obj2) + self.assertIs(oiddict['foobar'], obj2) + self.assertIs(oiddict['foo'], obj2) + self.assertIs(oiddict['bar'], obj2) + self.assertIs(oiddict[obj3], obj3) + self.assertIs(oiddict['1.1.2'], obj3) + self.assertIs(oiddict['test'], obj3) + self.assertEqual(len(oiddict), 3) + self.assertEqual(set(oiddict), {'1.1.0', 'fooBar', 'test'}) + self.assertIs(oiddict.get_numeric_oid(obj1), '1.1.0') + self.assertIs(oiddict.get_numeric_oid('1.1.0'), '1.1.0') + self.assertIs(oiddict.get_numeric_oid(obj2), '1.1.1') + self.assertIs(oiddict.get_numeric_oid('1.1.1'), '1.1.1') + self.assertIs(oiddict.get_numeric_oid('fooBar'), '1.1.1') + self.assertIs(oiddict.get_numeric_oid('foobar'), '1.1.1') + self.assertIs(oiddict.get_numeric_oid('foo'), '1.1.1') + self.assertIs(oiddict.get_numeric_oid('bar'), '1.1.1') + self.assertIs(oiddict.get_numeric_oid(obj3), '1.1.2') + self.assertIs(oiddict.get_numeric_oid('1.1.2'), '1.1.2') + self.assertIs(oiddict.get_numeric_oid('test'), '1.1.2') + + def test_uniqueness(self): + class TestObj: + pass + oiddict = OIDDict() + obj1 = TestObj() + oiddict._register(obj1, '1.1.0', 'foo', 'bar') + # Duplicate registration of the same obj is ok + oiddict._register(obj1, '1.1.0', 'foo', 'bar') + obj2 = TestObj() + # Duplicate registration of another obj with the same name/OID is not ok + with self.assertRaises(Exception): + oiddict._register(obj2, '1.1.0', 'fooBar') + with self.assertRaises(Exception): + oiddict._register(obj2, '1.1.1', 'fooBar', 'foo', 'test') + +class TestSchema(unittest.TestCase): + def test_syntax_registration(self): + schema = Schema(syntax_definitions=[syntaxes.DirectoryString]) + self.assertEqual(len(schema), 1) + self.assertEqual(len(schema.syntaxes), 1) + self.assertIn(syntaxes.DirectoryString.oid, schema) + self.assertIn(syntaxes.DirectoryString.oid, schema.syntaxes) + syntax = schema[syntaxes.DirectoryString.oid] + self.assertIs(syntax.schema, schema) + self.assertEqual(syntax.definition, syntaxes.DirectoryString) + self.assertEqual(syntax.oid, syntaxes.DirectoryString.oid) + self.assertEqual(syntax.ref, syntaxes.DirectoryString.oid) + self.assertEqual(syntax.compatible_matching_rules, set()) + + def test_matching_rule_registration(self): + syntax_definitions = [ + syntaxes.DirectoryString, + syntaxes.TelephoneNumber, + ] + matching_rule_definitions = [ + matching_rules.caseExactMatch, + matching_rules.telephoneNumberMatch, + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions) + self.assertEqual(len(schema), 4) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 2) + self.assertIn(matching_rules.caseExactMatch.oid, schema) + self.assertIn(matching_rules.caseExactMatch.oid, schema.matching_rules) + for name in matching_rules.caseExactMatch.name: + self.assertIn(name, schema.matching_rules) + matching_rule = schema[matching_rules.caseExactMatch.oid] + self.assertIs(matching_rule.schema, schema) + self.assertEqual(matching_rule.definition, matching_rules.caseExactMatch) + self.assertEqual(matching_rule.oid, matching_rules.caseExactMatch.oid) + self.assertIs(matching_rule.syntax, schema[syntaxes.DirectoryString.oid]) + self.assertEqual(matching_rule.names, matching_rules.caseExactMatch.name) + self.assertEqual(matching_rule.ref, matching_rules.caseExactMatch.name[0]) + self.assertEqual(matching_rule.compatible_syntaxes, {schema[syntaxes.TelephoneNumber.oid], + schema[syntaxes.DirectoryString.oid]}) + self.assertEqual(schema[syntaxes.TelephoneNumber.oid].compatible_matching_rules, {matching_rule, schema['telephoneNumberMatch']}) + self.assertEqual(schema[syntaxes.DirectoryString.oid].compatible_matching_rules, {matching_rule}) + + def test_matching_rule_registration_unmet_deps(self): + syntax_definitions = [ + syntaxes.TelephoneNumber, + ] + matching_rule_definitions = [ + matching_rules.caseExactMatch, + ] + with self.assertRaises(Exception): + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions) + + def test_attribute_type_registration(self): + syntax_definitions = [ + syntaxes.DirectoryString, + syntaxes.SubstringAssertion, + ] + matching_rule_definitions = [ + matching_rules.caseIgnoreMatch, + matching_rules.caseIgnoreSubstringsMatch, + matching_rules.caseExactMatch, + ] + attribute_type_definitions = [ + "( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + "( 2.5.4.3 NAME ( 'cn' 'commonName' ) SUP name )", + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions) + self.assertEqual(len(schema), 7) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 3) + self.assertEqual(len(schema.attribute_types), 2) + for name in ['2.5.4.3', 'cn', 'commonName']: + self.assertIn(name, schema) + self.assertIn(name, schema.attribute_types) + attribute_type = schema['cn'] + self.assertIs(attribute_type.schema, schema) + self.assertEqual(attribute_type.oid, '2.5.4.3') + self.assertEqual(attribute_type.names, ['cn', 'commonName']) + self.assertEqual(attribute_type.ref, 'cn') + self.assertEqual(attribute_type.sup, schema['name']) + self.assertEqual(attribute_type.subtypes, set()) + self.assertEqual(schema['name'].subtypes, {attribute_type}) + self.assertEqual(attribute_type.equality, schema['caseIgnoreMatch']) + self.assertIsNone(attribute_type.ordering) + self.assertEqual(attribute_type.substr, schema['caseIgnoreSubstringsMatch']) + self.assertFalse(attribute_type.is_operational) + self.assertIn(attribute_type, schema.user_attribute_types) + self.assertEqual(attribute_type.compatible_matching_rules, + {schema['caseIgnoreMatch'], schema['caseIgnoreSubstringsMatch'], schema['caseExactMatch']}) + self.assertIn(attribute_type, schema['caseIgnoreMatch'].compatible_attribute_types) + self.assertIn(attribute_type, schema['caseIgnoreSubstringsMatch'].compatible_attribute_types) + self.assertIn(attribute_type, schema['caseExactMatch'].compatible_attribute_types) + + def test_attribute_type_registration_wrong_order(self): + syntax_definitions = [ + syntaxes.DirectoryString, + syntaxes.SubstringAssertion, + ] + matching_rule_definitions = [ + matching_rules.caseIgnoreMatch, + matching_rules.caseIgnoreSubstringsMatch, + matching_rules.caseExactMatch, + ] + attribute_type_definitions = [ + "( 2.5.4.3 NAME ( 'cn' 'commonName' ) SUP name )", + "( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions) + self.assertEqual(len(schema), 7) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 3) + self.assertEqual(len(schema.attribute_types), 2) + + def test_object_class_registration(self): + syntax_definitions = [ + syntaxes.DN, + syntaxes.OID, + ] + matching_rule_definitions = [ + matching_rules.distinguishedNameMatch, + matching_rules.objectIdentifierMatch, + ] + attribute_type_definitions = [ + "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + ] + object_class_definitions = [ + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName )", + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions, + object_class_definitions=object_class_definitions) + self.assertEqual(len(schema), 8) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 2) + self.assertEqual(len(schema.attribute_types), 2) + self.assertEqual(len(schema.object_classes), 2) + + def test_object_class_registration_wrong_order(self): + syntax_definitions = [ + syntaxes.DN, + syntaxes.OID, + ] + matching_rule_definitions = [ + matching_rules.distinguishedNameMatch, + matching_rules.objectIdentifierMatch, + ] + attribute_type_definitions = [ + "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + ] + object_class_definitions = [ + "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName )", + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions, + object_class_definitions=object_class_definitions) + self.assertEqual(len(schema), 8) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 2) + self.assertEqual(len(schema.attribute_types), 2) + self.assertEqual(len(schema.object_classes), 2) + + def test_object_extend(self): + syntax_definitions = [ + syntaxes.DN, + syntaxes.OID, + ] + matching_rule_definitions = [ + matching_rules.distinguishedNameMatch, + matching_rules.objectIdentifierMatch, + ] + attribute_type_definitions = [ + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + ] + object_class_definitions = [ + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + ] + schema = Schema(syntax_definitions=syntax_definitions, + matching_rule_definitions=matching_rule_definitions, + attribute_type_definitions=attribute_type_definitions, + object_class_definitions=object_class_definitions) + self.assertEqual(len(schema), 6) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 2) + self.assertEqual(len(schema.attribute_types), 1) + self.assertEqual(len(schema.object_classes), 1) + attribute_type_definitions = [ + "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + ] + object_class_definitions = [ + "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName )", + ] + schema = schema.extend(attribute_type_definitions=attribute_type_definitions, + object_class_definitions=object_class_definitions) + self.assertEqual(len(schema), 8) + self.assertEqual(len(schema.syntaxes), 2) + self.assertEqual(len(schema.matching_rules), 2) + self.assertEqual(len(schema.attribute_types), 2) + self.assertEqual(len(schema.object_classes), 2) + self.assertEqual(schema['distinguishedNameMatch'].compatible_attribute_types, + {schema['aliasedObjectName']}) + + def test_or(self): + syntax_definitions0 = [ + syntaxes.DN, + syntaxes.OID, + syntaxes.DirectoryString, + syntaxes.SubstringAssertion, + ] + matching_rule_definitions0 = [ + matching_rules.distinguishedNameMatch, + matching_rules.objectIdentifierMatch, + matching_rules.caseIgnoreMatch, + matching_rules.caseIgnoreSubstringsMatch, + ] + attribute_type_definitions0 = [ + "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + "( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + ] + object_class_definitions0 = [ + "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName )", + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + ] + schema0 = Schema(syntax_definitions=syntax_definitions0, + matching_rule_definitions=matching_rule_definitions0, + attribute_type_definitions=attribute_type_definitions0, + object_class_definitions=object_class_definitions0) + syntax_definitions1 = [ + syntaxes.DirectoryString, + syntaxes.OID, + ] + matching_rule_definitions1 = [ + matching_rules.caseExactMatch, + matching_rules.objectIdentifierMatch, + ] + attribute_type_definitions1 = [ + "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + "( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' DESC 'Uniform Resource Identifier with optional label' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + ] + object_class_definitions1 = [ + "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass )", + "( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'object that contains the URI attribute type' SUP top AUXILIARY MAY labeledURI )", + ] + schema1 = Schema(syntax_definitions=syntax_definitions1, + matching_rule_definitions=matching_rule_definitions1, + attribute_type_definitions=attribute_type_definitions1, + object_class_definitions=object_class_definitions1) + schema = schema0 | schema1 + self.assertEqual(len(schema.syntaxes), 4) + self.assertEqual(len(schema.matching_rules), 5) + self.assertEqual(len(schema.attribute_types), 4) + self.assertEqual(len(schema.object_classes), 3) + +class TestAttributeType(unittest.TestCase): + schema = ldapserver.schema.RFC4519_SCHEMA + + def test_repr(self): + self.assertIsInstance(repr(self.schema['cn']), str) + + def test_encode(self): + self.assertEqual(self.schema['cn'].encode('foo äöü BAR'), b'foo \xc3\xa4\xc3\xb6\xc3\xbc BAR') + + def test_decode(self): + self.assertEqual(self.schema['cn'].decode(b'foo \xc3\xa4\xc3\xb6\xc3\xbc BAR'), 'foo äöü BAR') + + def test_match_equal(self): + self.assertFalse(self.schema['cn'].match_equal([], b'test')) + self.assertFalse(self.schema['cn'].match_equal(['foo'], b'test')) + self.assertTrue(self.schema['cn'].match_equal(['foo', 'bar', 'äöü'], b'\xc3\xa4\xc3\xb6\xc3\xbc ')) + self.assertTrue(self.schema['cn'].match_equal(['foo', 'bar', 'äöü'], b'BAR')) + # objectIdentifierMatch actually uses data from the self.schema + self.assertTrue(self.schema['objectclass'].match_equal(['2.5.6.0', 'Alias'], b'2.5.6.1')) + self.assertTrue(self.schema['objectclass'].match_equal(['2.5.6.0', 'Alias'], b'tOp')) + # 'facsimileTelephoneNumber' has no EQUALITY + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['facsimileTelephoneNumber'].match_equal([b'test'], b'test') + + def test_match_substr(self): + self.assertFalse(self.schema['cn'].match_substr([], b'test', [], None)) + self.assertFalse(self.schema['cn'].match_substr(['foo'], b'test', [], None)) + self.assertTrue(self.schema['cn'].match_substr(['foo', 'bar', 'äöü'], b'\xc3\xa4', [], None)) + self.assertTrue(self.schema['cn'].match_substr(['foo', 'bar', 'äöü'], None, [b'BA'], b'r ')) + # 'facsimileTelephoneNumber' has no SUBSTR + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['facsimileTelephoneNumber'].match_substr([b'test'], b'test', [], None) + + def test_match_approx(self): + # We don't have any matching rule that implementes a separate approx match, so ... + self.assertFalse(self.schema['cn'].match_approx([], b'test')) + self.assertFalse(self.schema['cn'].match_equal(['foo'], b'test')) + self.assertTrue(self.schema['cn'].match_approx(['foo', 'bar', 'äöü'], b'\xc3\xa4\xc3\xb6\xc3\xbc ')) + self.assertTrue(self.schema['cn'].match_approx(['foo', 'bar', 'äöü'], b'BAR')) + # objectIdentifierMatch actually uses data from the schema + self.assertTrue(self.schema['objectclass'].match_approx(['2.5.6.0', 'Alias'], b'2.5.6.1')) + self.assertTrue(self.schema['objectclass'].match_approx(['2.5.6.0', 'Alias'], b'tOp')) + # 'facsimileTelephoneNumber' has no EQUALITY + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['facsimileTelephoneNumber'].match_approx([b'test'], b'test') + + def test_match_greater_or_equal(self): + self.assertTrue(self.schema['createTimestamp'].match_greater_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700101000140Z')) + self.assertTrue(self.schema['createTimestamp'].match_greater_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700101000000Z')) + self.assertFalse(self.schema['createTimestamp'].match_greater_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700201000140Z')) + # 'cn' has no ORDERING + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['cn'].match_greater_or_equal(['test'], b'test') + + def test_match_less_or_equal(self): + self.assertTrue(self.schema['createTimestamp'].match_less_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700101000140Z')) + self.assertFalse(self.schema['createTimestamp'].match_less_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700101000000Z')) + self.assertTrue(self.schema['createTimestamp'].match_less_or_equal([datetime.datetime.fromtimestamp(100, datetime.timezone.utc)], b'19700201000140Z')) + # 'cn' has no ORDERING, but <= is a hybrid of ORDERING and EQUALITY + self.schema['cn'].match_less_or_equal(['test'], b'test') + # 'facsimileTelephoneNumber' has no EQUALITY/ORDERING + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['facsimileTelephoneNumber'].match_less_or_equal([b'test'], b'test') + + def test_match_extensible(self): + self.assertTrue(self.schema['cn'].match_extensible(['test'], b'Test', None)) + self.assertTrue(self.schema['cn'].match_extensible(['test'], b'Test', self.schema['caseIgnoreMatch'])) + self.assertFalse(self.schema['cn'].match_extensible(['test'], b'Test', self.schema['caseExactMatch'])) + self.assertTrue(self.schema['cn'].match_extensible(['test'], b'test', self.schema['caseExactMatch'])) + # 'facsimileTelephoneNumber' has no EQUALITY (with match_extensible defaults to) + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['facsimileTelephoneNumber'].match_extensible([b'test'], b'test', None) + # Incompatible matching + with self.assertRaises(ldapserver.exceptions.LDAPInappropriateMatching): + self.schema['cn'].match_extensible([b'test'], b'7', self.schema['integerMatch']) diff --git a/tests/test_schema_definitions.py b/tests/test_schema_definitions.py new file mode 100644 index 0000000000000000000000000000000000000000..1a998b00a680d2151a300e838b1a9228cb73c307 --- /dev/null +++ b/tests/test_schema_definitions.py @@ -0,0 +1,477 @@ +import unittest + +import ldapserver +from ldapserver.schema.definitions import SyntaxDefinition, MatchingRuleDefinition, MatchingRuleKind + +class TestSyntaxDefinition(unittest.TestCase): + def test_str(self): + self.assertEqual(str(SyntaxDefinition('1.2.3.4')), "( 1.2.3.4 )") + self.assertEqual(str(SyntaxDefinition('1.2.3.4', desc="")), "( 1.2.3.4 )") + self.assertEqual(str(SyntaxDefinition('1.2.3.4', desc="foo bar")), + "( 1.2.3.4 DESC 'foo bar' )") + self.assertEqual(str(SyntaxDefinition('1.2.3.4', desc="foo's bar")), + "( 1.2.3.4 DESC 'foo\\27s bar' )") + self.assertEqual(str(SyntaxDefinition('1.2.3.4', desc="foobar", extensions={'X-FOO': ['foo bar', 'foobar']})), + "( 1.2.3.4 DESC 'foobar' X-FOO ( 'foo bar' 'foobar' ) )") + + def test_first_component_oid(self): + self.assertEqual(SyntaxDefinition('1.2.3.4').oid, '1.2.3.4') + + def test_compatability_tags(self): + self.assertEqual(SyntaxDefinition('1.2.3.4').compatability_tags, {'1.2.3.4'}) + self.assertEqual(SyntaxDefinition('1.2.3.4', extra_compatability_tags={'4.3.2.1', 'foo'}).compatability_tags, {'1.2.3.4', '4.3.2.1', 'foo'}) + +syntax = ldapserver.schema.syntaxes.DirectoryString + +class TestMatchingRuleDefinition(unittest.TestCase): + def test_str(self): + self.assertEqual(str(MatchingRuleDefinition('1.2.3.4', name=['fooBarMatch'], desc="Matching rule's description", obsolete=True, syntax='1.3.6.1.4.1.1466.115.121.1.15', extensions={'X-FOO': ['foo bar', 'foobar']}, kind=MatchingRuleKind.EQUALITY)), + "( 1.2.3.4 NAME 'fooBarMatch' DESC 'Matching rule\\27s description' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-FOO ( 'foo bar' 'foobar' ) )") + self.assertEqual(str(MatchingRuleDefinition('1.2.3.4', name=['fooMatch', 'fooBarMatch'], syntax='1.3.6.1.4.1.1466.115.121.1.15', kind=MatchingRuleKind.EQUALITY)), + "( 1.2.3.4 NAME ( 'fooMatch' 'fooBarMatch' ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )") + + def test_first_component_oid(self): + self.assertEqual(MatchingRuleDefinition('1.2.3.4', syntax='1.3.6.1.4.1.1466.115.121.1.15', kind=MatchingRuleKind.EQUALITY).first_component_oid, '1.2.3.4') + + def test_compatability_tags(self): + self.assertEqual(MatchingRuleDefinition('1.2.3.4', syntax='1.3.6.1.4.1.1466.115.121.1.15', kind=MatchingRuleKind.EQUALITY).compatability_tag, '1.3.6.1.4.1.1466.115.121.1.15') + self.assertEqual(MatchingRuleDefinition('1.2.3.4', syntax='1.3.6.1.4.1.1466.115.121.1.15', compatability_tag='foo', kind=MatchingRuleKind.EQUALITY).compatability_tag, 'foo') + +class TestAttributeTypeDefinition(unittest.TestCase): + pass # TODO + +class TestObjectClassDefinition(unittest.TestCase): + pass # TODO + +class TestBuiltin(unittest.TestCase): + def test_encoding(self): + schemas = [ + ldapserver.schema.RFC4512_SCHEMA, + ldapserver.schema.RFC4519_SCHEMA, + ldapserver.schema.RFC4523_SCHEMA, + ldapserver.schema.RFC4524_SCHEMA, + ldapserver.schema.RFC3112_SCHEMA, + ldapserver.schema.RFC2079_SCHEMA, + ldapserver.schema.RFC2798_SCHEMA, + ldapserver.schema.RFC2307BIS_SCHEMA, + ] + for schema in schemas: + for obj in schema.syntax_definitions: + str(obj) + for obj in schema.matching_rule_definitions: + str(obj) + for obj in schema.attribute_type_definitions: + str(obj) + for obj in schema.object_class_definitions: + str(obj) + +# Test correctness and completeness of bundled schemas based on IANA registry +class TestOIDs(unittest.TestCase): + def test_core(self): + schema = ldapserver.schema.RFC4512_SCHEMA + # Matching rules (RFC4517) + self.assertIn('bitStringMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['bitStringMatch'].oid, '2.5.13.16') + self.assertIn('booleanMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['booleanMatch'].oid, '2.5.13.13') + self.assertIn('caseExactIA5Match', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseExactIA5Match'].oid, '1.3.6.1.4.1.1466.109.114.1') + self.assertIn('caseExactMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseExactMatch'].oid, '2.5.13.5') + self.assertIn('caseExactOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseExactOrderingMatch'].oid, '2.5.13.6') + self.assertIn('caseExactSubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseExactSubstringsMatch'].oid, '2.5.13.7') + self.assertIn('caseIgnoreIA5Match', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreIA5Match'].oid, '1.3.6.1.4.1.1466.109.114.2') + self.assertIn('caseIgnoreIA5SubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreIA5SubstringsMatch'].oid, '1.3.6.1.4.1.1466.109.114.3') + self.assertIn('caseIgnoreListMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreListMatch'].oid, '2.5.13.11') + self.assertIn('caseIgnoreListSubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreListSubstringsMatch'].oid, '2.5.13.12') + self.assertIn('caseIgnoreMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreMatch'].oid, '2.5.13.2') + self.assertIn('caseIgnoreOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreOrderingMatch'].oid, '2.5.13.3') + self.assertIn('caseIgnoreSubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['caseIgnoreSubstringsMatch'].oid, '2.5.13.4') + self.assertIn('directoryStringFirstComponentMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['directoryStringFirstComponentMatch'].oid, '2.5.13.31') + self.assertIn('distinguishedNameMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['distinguishedNameMatch'].oid, '2.5.13.1') + self.assertIn('generalizedTimeMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['generalizedTimeMatch'].oid, '2.5.13.27') + self.assertIn('generalizedTimeOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['generalizedTimeOrderingMatch'].oid, '2.5.13.28') + self.assertIn('integerFirstComponentMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['integerFirstComponentMatch'].oid, '2.5.13.29') + self.assertIn('integerMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['integerMatch'].oid, '2.5.13.14') + self.assertIn('integerOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['integerOrderingMatch'].oid, '2.5.13.15') + self.assertIn('keywordMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['keywordMatch'].oid, '2.5.13.33') + self.assertIn('numericStringMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['numericStringMatch'].oid, '2.5.13.8') + self.assertIn('numericStringOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['numericStringOrderingMatch'].oid, '2.5.13.9') + self.assertIn('numericStringSubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['numericStringSubstringsMatch'].oid, '2.5.13.10') + self.assertIn('objectIdentifierFirstComponentMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['objectIdentifierFirstComponentMatch'].oid, '2.5.13.30') + self.assertIn('objectIdentifierMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['objectIdentifierMatch'].oid, '2.5.13.0') + self.assertIn('octetStringMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['octetStringMatch'].oid, '2.5.13.17') + self.assertIn('octetStringOrderingMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['octetStringOrderingMatch'].oid, '2.5.13.18') + self.assertIn('telephoneNumberMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['telephoneNumberMatch'].oid, '2.5.13.20') + self.assertIn('telephoneNumberSubstringsMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['telephoneNumberSubstringsMatch'].oid, '2.5.13.21') + self.assertIn('uniqueMemberMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['uniqueMemberMatch'].oid, '2.5.13.23') + self.assertIn('wordMatch', schema.matching_rules) + self.assertEqual(schema.matching_rules['wordMatch'].oid, '2.5.13.32') + # Attribute types (RFC4512) + self.assertIn('aliasedObjectName', schema.attribute_types) + self.assertEqual(schema.attribute_types['aliasedObjectName'].oid, '2.5.4.1') + self.assertIn('altServer', schema.attribute_types) + self.assertEqual(schema.attribute_types['altServer'].oid, '1.3.6.1.4.1.1466.101.120.6') + self.assertIn('attributeTypes', schema.attribute_types) + self.assertEqual(schema.attribute_types['attributeTypes'].oid, '2.5.21.5') + self.assertIn('createTimestamp', schema.attribute_types) + self.assertEqual(schema.attribute_types['createTimestamp'].oid, '2.5.18.1') + self.assertIn('creatorsName', schema.attribute_types) + self.assertEqual(schema.attribute_types['creatorsName'].oid, '2.5.18.3') + self.assertIn('dITContentRules', schema.attribute_types) + self.assertEqual(schema.attribute_types['dITContentRules'].oid, '2.5.21.2') + self.assertIn('dITStructureRules', schema.attribute_types) + self.assertEqual(schema.attribute_types['dITStructureRules'].oid, '2.5.21.1') + self.assertIn('governingStructureRule', schema.attribute_types) + self.assertEqual(schema.attribute_types['governingStructureRule'].oid, '2.5.21.10') + self.assertIn('ldapSyntaxes', schema.attribute_types) + self.assertEqual(schema.attribute_types['ldapSyntaxes'].oid, '1.3.6.1.4.1.1466.101.120.16') + self.assertIn('matchingRules', schema.attribute_types) + self.assertEqual(schema.attribute_types['matchingRules'].oid, '2.5.21.4') + self.assertIn('matchingRuleUse', schema.attribute_types) + self.assertEqual(schema.attribute_types['matchingRuleUse'].oid, '2.5.21.8') + self.assertIn('modifiersName', schema.attribute_types) + self.assertEqual(schema.attribute_types['modifiersName'].oid, '2.5.18.4') + self.assertIn('modifyTimestamp', schema.attribute_types) + self.assertEqual(schema.attribute_types['modifyTimestamp'].oid, '2.5.18.2') + self.assertIn('nameForms', schema.attribute_types) + self.assertEqual(schema.attribute_types['nameForms'].oid, '2.5.21.7') + self.assertIn('namingContexts', schema.attribute_types) + self.assertEqual(schema.attribute_types['namingContexts'].oid, '1.3.6.1.4.1.1466.101.120.5') + self.assertIn('objectClass', schema.attribute_types) + self.assertEqual(schema.attribute_types['objectClass'].oid, '2.5.4.0') + self.assertIn('objectClasses', schema.attribute_types) + self.assertEqual(schema.attribute_types['objectClasses'].oid, '2.5.21.6') + self.assertIn('structuralObjectClass', schema.attribute_types) + self.assertEqual(schema.attribute_types['structuralObjectClass'].oid, '2.5.21.9') + self.assertIn('subschemaSubentry', schema.attribute_types) + self.assertEqual(schema.attribute_types['subschemaSubentry'].oid, '2.5.18.10') + self.assertIn('supportedControl', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedControl'].oid, '1.3.6.1.4.1.1466.101.120.13') + self.assertIn('supportedExtension', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedExtension'].oid, '1.3.6.1.4.1.1466.101.120.7') + self.assertIn('supportedFeatures', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedFeatures'].oid, '1.3.6.1.4.1.4203.1.3.5') + self.assertIn('supportedLDAPVersion', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedLDAPVersion'].oid, '1.3.6.1.4.1.1466.101.120.15') + self.assertIn('supportedSASLMechanisms', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedSASLMechanisms'].oid, '1.3.6.1.4.1.1466.101.120.14') + # Object classes (RFC4512) + self.assertIn('alias', schema.object_classes) + self.assertEqual(schema.object_classes['alias'].oid, '2.5.6.1') + self.assertIn('extensibleObject', schema.object_classes) + self.assertEqual(schema.object_classes['extensibleObject'].oid, '1.3.6.1.4.1.1466.101.120.111') + self.assertIn('subschema', schema.object_classes) + self.assertEqual(schema.object_classes['subschema'].oid, '2.5.20.1') + self.assertIn('top', schema.object_classes) + self.assertEqual(schema.object_classes['top'].oid, '2.5.6.0') + + def test_rfc4519(self): + schema = ldapserver.schema.RFC4519_SCHEMA + # Attribute types + self.assertIn('businessCategory', schema.attribute_types) + self.assertEqual(schema.attribute_types['businessCategory'].oid, '2.5.4.15') + self.assertIn('c', schema.attribute_types) + self.assertEqual(schema.attribute_types['c'].oid, '2.5.4.6') + self.assertIn('cn', schema.attribute_types) + self.assertEqual(schema.attribute_types['cn'].oid, '2.5.4.3') + self.assertIn('commonName', schema.attribute_types) + self.assertEqual(schema.attribute_types['commonName'].oid, '2.5.4.3') + self.assertIn('countryName', schema.attribute_types) + self.assertEqual(schema.attribute_types['countryName'].oid, '2.5.4.6') + self.assertIn('DC', schema.attribute_types) + self.assertEqual(schema.attribute_types['DC'].oid, '0.9.2342.19200300.100.1.25') + self.assertIn('description', schema.attribute_types) + self.assertEqual(schema.attribute_types['description'].oid, '2.5.4.13') + self.assertIn('destinationIndicator', schema.attribute_types) + self.assertEqual(schema.attribute_types['destinationIndicator'].oid, '2.5.4.27') + self.assertIn('distinguishedName', schema.attribute_types) + self.assertEqual(schema.attribute_types['distinguishedName'].oid, '2.5.4.49') + self.assertIn('dnQualifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['dnQualifier'].oid, '2.5.4.46') + self.assertIn('domainComponent', schema.attribute_types) + self.assertEqual(schema.attribute_types['domainComponent'].oid, '0.9.2342.19200300.100.1.25') + self.assertIn('enhancedSearchGuide', schema.attribute_types) + self.assertEqual(schema.attribute_types['enhancedSearchGuide'].oid, '2.5.4.47') + self.assertIn('facsimileTelephoneNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['facsimileTelephoneNumber'].oid, '2.5.4.23') + self.assertIn('generationQualifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['generationQualifier'].oid, '2.5.4.44') + self.assertIn('givenName', schema.attribute_types) + self.assertEqual(schema.attribute_types['givenName'].oid, '2.5.4.42') + self.assertIn('houseIdentifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['houseIdentifier'].oid, '2.5.4.51') + self.assertIn('initials', schema.attribute_types) + self.assertEqual(schema.attribute_types['initials'].oid, '2.5.4.43') + self.assertIn('internationaliSDNNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['internationaliSDNNumber'].oid, '2.5.4.25') + self.assertIn('L', schema.attribute_types) + self.assertEqual(schema.attribute_types['L'].oid, '2.5.4.7') + self.assertIn('localityName', schema.attribute_types) + self.assertEqual(schema.attribute_types['localityName'].oid, '2.5.4.7') + self.assertIn('member', schema.attribute_types) + self.assertEqual(schema.attribute_types['member'].oid, '2.5.4.31') + self.assertIn('name', schema.attribute_types) + self.assertEqual(schema.attribute_types['name'].oid, '2.5.4.41') + self.assertIn('o', schema.attribute_types) + self.assertEqual(schema.attribute_types['o'].oid, '2.5.4.10') + self.assertIn('organizationalUnitName', schema.attribute_types) + self.assertEqual(schema.attribute_types['organizationalUnitName'].oid, '2.5.4.11') + self.assertIn('organizationName', schema.attribute_types) + self.assertEqual(schema.attribute_types['organizationName'].oid, '2.5.4.10') + self.assertIn('ou', schema.attribute_types) + self.assertEqual(schema.attribute_types['ou'].oid, '2.5.4.11') + self.assertIn('owner', schema.attribute_types) + self.assertEqual(schema.attribute_types['owner'].oid, '2.5.4.32') + self.assertIn('physicalDeliveryOfficeName', schema.attribute_types) + self.assertEqual(schema.attribute_types['physicalDeliveryOfficeName'].oid, '2.5.4.19') + self.assertIn('postalAddress', schema.attribute_types) + self.assertEqual(schema.attribute_types['postalAddress'].oid, '2.5.4.16') + self.assertIn('postalCode', schema.attribute_types) + self.assertEqual(schema.attribute_types['postalCode'].oid, '2.5.4.17') + self.assertIn('postOfficeBox', schema.attribute_types) + self.assertEqual(schema.attribute_types['postOfficeBox'].oid, '2.5.4.18') + self.assertIn('preferredDeliveryMethod', schema.attribute_types) + self.assertEqual(schema.attribute_types['preferredDeliveryMethod'].oid, '2.5.4.28') + self.assertIn('registeredAddress', schema.attribute_types) + self.assertEqual(schema.attribute_types['registeredAddress'].oid, '2.5.4.26') + self.assertIn('roleOccupant', schema.attribute_types) + self.assertEqual(schema.attribute_types['roleOccupant'].oid, '2.5.4.33') + self.assertIn('searchGuide', schema.attribute_types) + self.assertEqual(schema.attribute_types['searchGuide'].oid, '2.5.4.14') + self.assertIn('seeAlso', schema.attribute_types) + self.assertEqual(schema.attribute_types['seeAlso'].oid, '2.5.4.34') + self.assertIn('serialNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['serialNumber'].oid, '2.5.4.5') + self.assertIn('sn', schema.attribute_types) + self.assertEqual(schema.attribute_types['sn'].oid, '2.5.4.4') + self.assertIn('st', schema.attribute_types) + self.assertEqual(schema.attribute_types['st'].oid, '2.5.4.8') + self.assertIn('street', schema.attribute_types) + self.assertEqual(schema.attribute_types['street'].oid, '2.5.4.9') + self.assertIn('surname', schema.attribute_types) + self.assertEqual(schema.attribute_types['surname'].oid, '2.5.4.4') + self.assertIn('telephoneNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['telephoneNumber'].oid, '2.5.4.20') + self.assertIn('teletexTerminalIdentifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['teletexTerminalIdentifier'].oid, '2.5.4.22') + self.assertIn('telexNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['telexNumber'].oid, '2.5.4.21') + self.assertIn('title', schema.attribute_types) + self.assertEqual(schema.attribute_types['title'].oid, '2.5.4.12') + self.assertIn('uid', schema.attribute_types) + self.assertEqual(schema.attribute_types['uid'].oid, '0.9.2342.19200300.100.1.1') + self.assertIn('uniqueMember', schema.attribute_types) + self.assertEqual(schema.attribute_types['uniqueMember'].oid, '2.5.4.50') + self.assertIn('userId', schema.attribute_types) + self.assertEqual(schema.attribute_types['userId'].oid, '0.9.2342.19200300.100.1.1') + self.assertIn('userPassword', schema.attribute_types) + self.assertEqual(schema.attribute_types['userPassword'].oid, '2.5.4.35') + self.assertIn('x121Address', schema.attribute_types) + self.assertEqual(schema.attribute_types['x121Address'].oid, '2.5.4.24') + self.assertIn('x500UniqueIdentifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['x500UniqueIdentifier'].oid, '2.5.4.45') + # Object classes + self.assertIn('applicationProcess', schema.object_classes) + self.assertEqual(schema.object_classes['applicationProcess'].oid, '2.5.6.11') + self.assertIn('country', schema.object_classes) + self.assertEqual(schema.object_classes['country'].oid, '2.5.6.2') + self.assertIn('dcObject', schema.object_classes) + self.assertEqual(schema.object_classes['dcObject'].oid, '1.3.6.1.4.1.1466.344') + self.assertIn('device', schema.object_classes) + self.assertEqual(schema.object_classes['device'].oid, '2.5.6.14') + self.assertIn('groupOfNames', schema.object_classes) + self.assertEqual(schema.object_classes['groupOfNames'].oid, '2.5.6.9') + self.assertIn('groupOfUniqueNames', schema.object_classes) + self.assertEqual(schema.object_classes['groupOfUniqueNames'].oid, '2.5.6.17') + self.assertIn('locality', schema.object_classes) + self.assertEqual(schema.object_classes['locality'].oid, '2.5.6.3') + self.assertIn('organization', schema.object_classes) + self.assertEqual(schema.object_classes['organization'].oid, '2.5.6.4') + self.assertIn('organizationalPerson', schema.object_classes) + self.assertEqual(schema.object_classes['organizationalPerson'].oid, '2.5.6.7') + self.assertIn('organizationalRole', schema.object_classes) + self.assertEqual(schema.object_classes['organizationalRole'].oid, '2.5.6.8') + self.assertIn('organizationalUnit', schema.object_classes) + self.assertEqual(schema.object_classes['organizationalUnit'].oid, '2.5.6.5') + self.assertIn('person', schema.object_classes) + self.assertEqual(schema.object_classes['person'].oid, '2.5.6.6') + self.assertIn('residentialPerson', schema.object_classes) + self.assertEqual(schema.object_classes['residentialPerson'].oid, '2.5.6.10') + self.assertIn('uidObject', schema.object_classes) + self.assertEqual(schema.object_classes['uidObject'].oid, '1.3.6.1.1.3.1') + + def test_rfc4523(self): + schema = ldapserver.schema.RFC4523_SCHEMA + # Attribute types + self.assertIn('authorityRevocationList', schema.attribute_types) + self.assertEqual(schema.attribute_types['authorityRevocationList'].oid, '2.5.4.38') + self.assertIn('cACertificate', schema.attribute_types) + self.assertEqual(schema.attribute_types['cACertificate'].oid, '2.5.4.37') + self.assertIn('certificateRevocationList', schema.attribute_types) + self.assertEqual(schema.attribute_types['certificateRevocationList'].oid, '2.5.4.39') + self.assertIn('crossCertificatePair', schema.attribute_types) + self.assertEqual(schema.attribute_types['crossCertificatePair'].oid, '2.5.4.40') + self.assertIn('deltaRevocationList', schema.attribute_types) + self.assertEqual(schema.attribute_types['deltaRevocationList'].oid, '2.5.4.53') + self.assertIn('supportedAlgorithms', schema.attribute_types) + self.assertEqual(schema.attribute_types['supportedAlgorithms'].oid, '2.5.4.52') + self.assertIn('userCertificate', schema.attribute_types) + self.assertEqual(schema.attribute_types['userCertificate'].oid, '2.5.4.36') + # Object class + self.assertIn('certificationAuthority', schema.object_classes) + self.assertEqual(schema.object_classes['certificationAuthority'].oid, '2.5.6.16') + self.assertIn('certificationAuthority-V2', schema.object_classes) + self.assertEqual(schema.object_classes['certificationAuthority-V2'].oid, '2.5.6.16.2') + self.assertIn('cRLDistributionPoint', schema.object_classes) + self.assertEqual(schema.object_classes['cRLDistributionPoint'].oid, '2.5.6.19') + self.assertIn('deltaCRL', schema.object_classes) + self.assertEqual(schema.object_classes['deltaCRL'].oid, '2.5.6.23') + self.assertIn('pkiCA', schema.object_classes) + self.assertEqual(schema.object_classes['pkiCA'].oid, '2.5.6.22') + self.assertIn('pkiUser', schema.object_classes) + self.assertEqual(schema.object_classes['pkiUser'].oid, '2.5.6.21') + self.assertIn('strongAuthenticationUser', schema.object_classes) + self.assertEqual(schema.object_classes['strongAuthenticationUser'].oid, '2.5.6.15') + self.assertIn('userSecurityInformation', schema.object_classes) + self.assertEqual(schema.object_classes['userSecurityInformation'].oid, '2.5.6.18') + + def test_rfc4524(self): + schema = ldapserver.schema.RFC4524_SCHEMA + # Attribute types + self.assertIn('associatedDomain', schema.attribute_types) + self.assertEqual(schema.attribute_types['associatedDomain'].oid, '0.9.2342.19200300.100.1.37') + self.assertIn('associatedName', schema.attribute_types) + self.assertEqual(schema.attribute_types['associatedName'].oid, '0.9.2342.19200300.100.1.38') + self.assertIn('buildingName', schema.attribute_types) + self.assertEqual(schema.attribute_types['buildingName'].oid, '0.9.2342.19200300.100.1.48') + self.assertIn('co', schema.attribute_types) + self.assertEqual(schema.attribute_types['co'].oid, '0.9.2342.19200300.100.1.43') + self.assertIn('documentAuthor', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentAuthor'].oid, '0.9.2342.19200300.100.1.14') + self.assertIn('documentIdentifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentIdentifier'].oid, '0.9.2342.19200300.100.1.11') + self.assertIn('documentLocation', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentLocation'].oid, '0.9.2342.19200300.100.1.15') + self.assertIn('documentPublisher', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentPublisher'].oid, '0.9.2342.19200300.100.1.56') + self.assertIn('documentTitle', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentTitle'].oid, '0.9.2342.19200300.100.1.12') + self.assertIn('documentVersion', schema.attribute_types) + self.assertEqual(schema.attribute_types['documentVersion'].oid, '0.9.2342.19200300.100.1.13') + self.assertIn('drink', schema.attribute_types) + self.assertEqual(schema.attribute_types['drink'].oid, '0.9.2342.19200300.100.1.5') + self.assertIn('homePhone', schema.attribute_types) + self.assertEqual(schema.attribute_types['homePhone'].oid, '0.9.2342.19200300.100.1.20') + self.assertIn('homePostalAddress', schema.attribute_types) + self.assertEqual(schema.attribute_types['homePostalAddress'].oid, '0.9.2342.19200300.100.1.39') + self.assertIn('host', schema.attribute_types) + self.assertEqual(schema.attribute_types['host'].oid, '0.9.2342.19200300.100.1.9') + self.assertIn('info', schema.attribute_types) + self.assertEqual(schema.attribute_types['info'].oid, '0.9.2342.19200300.100.1.4') + self.assertIn('mail', schema.attribute_types) + self.assertEqual(schema.attribute_types['mail'].oid, '0.9.2342.19200300.100.1.3') + self.assertIn('manager', schema.attribute_types) + self.assertEqual(schema.attribute_types['manager'].oid, '0.9.2342.19200300.100.1.10') + self.assertIn('mobile', schema.attribute_types) + self.assertEqual(schema.attribute_types['mobile'].oid, '0.9.2342.19200300.100.1.41') + self.assertIn('organizationalStatus', schema.attribute_types) + self.assertEqual(schema.attribute_types['organizationalStatus'].oid, '0.9.2342.19200300.100.1.45') + self.assertIn('pager', schema.attribute_types) + self.assertEqual(schema.attribute_types['pager'].oid, '0.9.2342.19200300.100.1.42') + self.assertIn('personalTitle', schema.attribute_types) + self.assertEqual(schema.attribute_types['personalTitle'].oid, '0.9.2342.19200300.100.1.40') + self.assertIn('roomNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['roomNumber'].oid, '0.9.2342.19200300.100.1.6') + self.assertIn('secretary', schema.attribute_types) + self.assertEqual(schema.attribute_types['secretary'].oid, '0.9.2342.19200300.100.1.21') + # RFC4524 does not define singleLevelQuality + #self.assertIn('singleLevelQuality', schema.attribute_types) + #self.assertEqual(schema.attribute_types['singleLevelQuality'].oid, '0.9.2342.19200300.100.1.50') + self.assertIn('uniqueIdentifier', schema.attribute_types) + self.assertEqual(schema.attribute_types['uniqueIdentifier'].oid, '0.9.2342.19200300.100.1.44') + self.assertIn('userClass', schema.attribute_types) + self.assertEqual(schema.attribute_types['userClass'].oid, '0.9.2342.19200300.100.1.8') + # Object class + self.assertIn('account', schema.object_classes) + self.assertEqual(schema.object_classes['account'].oid, '0.9.2342.19200300.100.4.5') + self.assertIn('document', schema.object_classes) + self.assertEqual(schema.object_classes['document'].oid, '0.9.2342.19200300.100.4.6') + # RFC4524 is inconsistent regarding RFC4524: + # - It defines the attribute type with 0.9.2342.19200300.100.4.8 + # - It updates the IANA registration with 0.9.2342.19200300.100.4.9 + self.assertIn('documentSeries', schema.object_classes) + self.assertEqual(schema.object_classes['documentSeries'].oid, '0.9.2342.19200300.100.4.9') + self.assertIn('domain', schema.object_classes) + self.assertEqual(schema.object_classes['domain'].oid, '0.9.2342.19200300.100.4.13') + self.assertIn('domainRelatedObject', schema.object_classes) + self.assertEqual(schema.object_classes['domainRelatedObject'].oid, '0.9.2342.19200300.100.4.17') + self.assertIn('friendlyCountry', schema.object_classes) + self.assertEqual(schema.object_classes['friendlyCountry'].oid, '0.9.2342.19200300.100.4.18') + self.assertIn('RFC822LocalPart', schema.object_classes) + self.assertEqual(schema.object_classes['RFC822LocalPart'].oid, '0.9.2342.19200300.100.4.14') + self.assertIn('room', schema.object_classes) + self.assertEqual(schema.object_classes['room'].oid, '0.9.2342.19200300.100.4.7') + self.assertIn('simpleSecurityObject', schema.object_classes) + self.assertEqual(schema.object_classes['simpleSecurityObject'].oid, '0.9.2342.19200300.100.4.19') + + def test_rfc2079(self): + schema = ldapserver.schema.RFC2079_SCHEMA + # Attribute types + self.assertIn('labeledURI', schema.attribute_types) + self.assertEqual(schema.attribute_types['labeledURI'].oid, '1.3.6.1.4.1.250.1.57') + # Object class + self.assertIn('labeledURIObject', schema.object_classes) + self.assertEqual(schema.object_classes['labeledURIObject'].oid, '1.3.6.1.4.1.250.3.15') + + def test_rfc2798(self): + schema = ldapserver.schema.RFC2798_SCHEMA + # Attribute types + self.assertIn('carLicense', schema.attribute_types) + self.assertEqual(schema.attribute_types['carLicense'].oid, '2.16.840.1.113730.3.1.1') + self.assertIn('departmentNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['departmentNumber'].oid, '2.16.840.1.113730.3.1.2') + self.assertIn('displayName', schema.attribute_types) + self.assertEqual(schema.attribute_types['displayName'].oid, '2.16.840.1.113730.3.1.241') + self.assertIn('employeeNumber', schema.attribute_types) + self.assertEqual(schema.attribute_types['employeeNumber'].oid, '2.16.840.1.113730.3.1.3') + self.assertIn('employeeType', schema.attribute_types) + self.assertEqual(schema.attribute_types['employeeType'].oid, '2.16.840.1.113730.3.1.4') + self.assertIn('jpegPhoto', schema.attribute_types) + self.assertEqual(schema.attribute_types['jpegPhoto'].oid, '0.9.2342.19200300.100.1.60') + self.assertIn('preferredLanguage', schema.attribute_types) + self.assertEqual(schema.attribute_types['preferredLanguage'].oid, '2.16.840.1.113730.3.1.39') + self.assertIn('userPKCS12', schema.attribute_types) + self.assertEqual(schema.attribute_types['userPKCS12'].oid, '2.16.840.1.113730.3.1.216') + self.assertIn('userSMIMECertificate', schema.attribute_types) + self.assertEqual(schema.attribute_types['userSMIMECertificate'].oid, '2.16.840.1.113730.3.1.40') + # Object class + self.assertIn('inetOrgPerson', schema.object_classes) + self.assertEqual(schema.object_classes['inetOrgPerson'].oid, '2.16.840.1.113730.3.2.2') diff --git a/tests/test_schema_matching_rule.py b/tests/test_schema_matching_rule.py new file mode 100644 index 0000000000000000000000000000000000000000..a740ed0220b9c3548713f2ae2626ee3501479080 --- /dev/null +++ b/tests/test_schema_matching_rule.py @@ -0,0 +1,182 @@ +import unittest + +import ldapserver +from ldapserver.schema import matching_rules + +class TestGenericEqualityMatchingRule(unittest.TestCase): + def test_match_equal(self): + rule = matching_rules.integerMatch + self.assertTrue(rule.match_equal(None, [1234], 1234)) + self.assertFalse(rule.match_equal(None, [4321], 1234)) + self.assertFalse(rule.match_equal(None, [1234], 4321)) + self.assertTrue(rule.match_equal(None, [0, 1], 0)) + self.assertTrue(rule.match_equal(None, [0, 1], 1)) + self.assertFalse(rule.match_equal(None, [0, 1], -1)) + self.assertFalse(rule.match_equal(None, [0, 1], 2)) + self.assertFalse(rule.match_equal(None, [], 1)) + +class TestGenericOrderingMatchingRule(unittest.TestCase): + def test_match_less(self): + rule = matching_rules.integerOrderingMatch + self.assertFalse(rule.match_less(None, [1234], 1234)) + self.assertFalse(rule.match_less(None, [4321], 1234)) + self.assertTrue(rule.match_less(None, [1234], 4321)) + self.assertFalse(rule.match_less(None, [0, 1], 0)) + self.assertTrue(rule.match_less(None, [0, 1], 1)) + self.assertFalse(rule.match_less(None, [0, 1], -1)) + self.assertTrue(rule.match_less(None, [0, 1], 2)) + self.assertFalse(rule.match_less(None, [], 1)) + + def test_match_greater_or_equal(self): + rule = matching_rules.integerOrderingMatch + self.assertTrue(rule.match_greater_or_equal(None, [1234], 1234)) + self.assertTrue(rule.match_greater_or_equal(None, [4321], 1234)) + self.assertFalse(rule.match_greater_or_equal(None, [1234], 4321)) + self.assertTrue(rule.match_greater_or_equal(None, [0, 1], 0)) + self.assertTrue(rule.match_greater_or_equal(None, [0, 1], 1)) + self.assertTrue(rule.match_greater_or_equal(None, [0, 1], -1)) + self.assertFalse(rule.match_greater_or_equal(None, [0, 1], 2)) + self.assertFalse(rule.match_greater_or_equal(None, [], 1)) + +class TestStringEqualityMatchingRule(unittest.TestCase): + def test_match_equal(self): + rule = matching_rules.caseIgnoreMatch + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'foo')) + self.assertFalse(rule.match_equal(None, ['foo', 'Bar'], 'foobar')) + self.assertFalse(rule.match_equal(None, [], 'foo')) + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'Bar')) + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'Foo')) + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'bar')) + self.assertTrue(rule.match_equal(None, ['fo o ', ' bar'], ' bar ')) + self.assertFalse(rule.match_equal(None, ['fo o ', ' b ar'], ' bar ')) + self.assertTrue(rule.match_equal(None, ['fo\n\ro ', ' bar'], ' fo o')) + rule = matching_rules.caseExactMatch + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'foo')) + self.assertFalse(rule.match_equal(None, ['foo', 'Bar'], 'foobar')) + self.assertFalse(rule.match_equal(None, [], 'foo')) + self.assertTrue(rule.match_equal(None, ['foo', 'Bar'], 'Bar')) + self.assertFalse(rule.match_equal(None, ['foo', 'Bar'], 'Foo')) + self.assertFalse(rule.match_equal(None, ['foo', 'Bar'], 'bar')) + self.assertTrue(rule.match_equal(None, ['fo o ', ' bar'], ' bar ')) + self.assertFalse(rule.match_equal(None, ['fo o ', ' b ar'], ' bar ')) + self.assertTrue(rule.match_equal(None, ['fo\n\ro ', ' bar'], ' fo o')) + # Systematic tests for stringprep in test_stringprep.py + +class TestStringOrderingMatchingRule(unittest.TestCase): + def test_match_less(self): + rule = matching_rules.caseIgnoreOrderingMatch + self.assertFalse(rule.match_less(None, ['abc'], 'abc')) + self.assertFalse(rule.match_less(None, [], 'abc')) + self.assertTrue(rule.match_less(None, ['abc'], 'def')) + self.assertTrue(rule.match_less(None, ['abc'], 'acd')) + self.assertTrue(rule.match_less(None, ['def', 'abc'], 'acd')) + self.assertTrue(rule.match_less(None, ['A'], 'b')) + self.assertFalse(rule.match_less(None, ['a'], 'A')) + self.assertFalse(rule.match_less(None, ['C'], 'a')) + rule = matching_rules.caseExactOrderingMatch + self.assertFalse(rule.match_less(None, ['abc'], 'abc')) + self.assertFalse(rule.match_less(None, [], 'abc')) + self.assertTrue(rule.match_less(None, ['abc'], 'def')) + self.assertTrue(rule.match_less(None, ['abc'], 'acd')) + self.assertTrue(rule.match_less(None, ['def', 'abc'], 'acd')) + self.assertTrue(rule.match_less(None, ['A'], 'b')) + self.assertFalse(rule.match_less(None, ['a'], 'A')) + self.assertTrue(rule.match_less(None, ['C'], 'a')) + # Systematic tests for stringprep in test_stringprep.py + + def test_match_greater_or_equal(self): + rule = matching_rules.caseIgnoreOrderingMatch + self.assertTrue(rule.match_greater_or_equal(None, ['abc'], 'abc')) + self.assertFalse(rule.match_greater_or_equal(None, [], 'abc')) + self.assertFalse(rule.match_greater_or_equal(None, ['abc'], 'def')) + self.assertFalse(rule.match_greater_or_equal(None, ['abc'], 'acd')) + self.assertTrue(rule.match_greater_or_equal(None, ['def', 'abc'], 'acd')) + self.assertFalse(rule.match_greater_or_equal(None, ['A'], 'b')) + self.assertTrue(rule.match_greater_or_equal(None, ['a'], 'A')) + self.assertTrue(rule.match_greater_or_equal(None, ['C'], 'a')) + rule = matching_rules.caseExactOrderingMatch + self.assertTrue(rule.match_greater_or_equal(None, ['abc'], 'abc')) + self.assertFalse(rule.match_greater_or_equal(None, [], 'abc')) + self.assertFalse(rule.match_greater_or_equal(None, ['abc'], 'def')) + self.assertFalse(rule.match_greater_or_equal(None, ['abc'], 'acd')) + self.assertTrue(rule.match_greater_or_equal(None, ['def', 'abc'], 'acd')) + self.assertFalse(rule.match_greater_or_equal(None, ['A'], 'b')) + self.assertTrue(rule.match_greater_or_equal(None, ['a'], 'A')) + self.assertFalse(rule.match_greater_or_equal(None, ['C'], 'a')) + # Systematic tests for stringprep in test_stringprep.py + +class TestStringSubstrMatchingRule(unittest.TestCase): + def test_match_substr(self): + rule = matching_rules.caseExactSubstringsMatch + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abcdefghi', [], None)) + self.assertTrue(rule.match_substr(None, ['foo', 'abcdefghi', 'bar'], 'abcdefghi', [], None)) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], None, ['abcdefghi'], None)) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], None, [], 'abcdefghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abc', ['def'], 'ghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abc', ['d', 'ef'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abcd', ['d', 'ef'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abc', ['cd', 'ef'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abc', ['de', 'ef'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abc', ['d', 'def'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abc', ['d', 'efg'], 'ghi')) + self.assertFalse(rule.match_substr(None, ['abcdefghi'], 'abc', ['d', 'ef'], 'fghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'ab', ['def'], 'ghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abc', ['ef'], 'ghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abc', ['de'], 'ghi')) + self.assertTrue(rule.match_substr(None, ['abcdefghi'], 'abc', ['def'], 'hi')) + # TODO: more systematic tests + +class TestStringListEqualityMatchingRule(unittest.TestCase): + def test_equal(self): + rule = matching_rules.caseIgnoreListMatch + self.assertFalse(rule.match_equal(None, [], ['foo', 'bar'])) + self.assertTrue(rule.match_equal(None, [['foo', 'bar']], ['foo', 'bar'])) + self.assertTrue(rule.match_equal(None, [['Foo', 'bar']], ['foo', 'BAR'])) + self.assertFalse(rule.match_equal(None, [['bar', 'foo']], ['foo', 'bar'])) + self.assertFalse(rule.match_equal(None, [['foo', 'bar']], ['foo'])) + self.assertTrue(rule.match_equal(None, [['first'], ['foo', 'bar']], ['foo', 'bar'])) + self.assertTrue(rule.match_equal(None, [['line'], ['foo', 'bar']], ['line'])) + +class TestStringListSubstrMatchingRule(unittest.TestCase): + def test_match_substr(self): + rule = matching_rules.caseIgnoreListSubstringsMatch + self.assertFalse(rule.match_substr(None, [], None, ['foo'], None)) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], 'foo', ['bar'], 'baz')) + self.assertFalse(rule.match_substr(None, [['foo', 'bar', 'baz']], 'bar', [], None)) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], 'FOO', [], None)) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], None, ['bar'], 'baz')) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], 'foo', [], 'baz')) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], 'foo', ['bar'], None)) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], None, ['foo', 'bar', 'baz'], None)) + self.assertTrue(rule.match_substr(None, [['foo', 'bar', 'baz']], 'f', ['b', 'r'], 'z')) + self.assertTrue(rule.match_substr(None, [['foo', 'bar']], None, ['foo', 'bar'], None)) + self.assertFalse(rule.match_substr(None, [['foo', 'bar']], None, ['foobar'], None)) + self.assertFalse(rule.match_substr(None, [['foo', 'bar']], None, ['foo bar'], None)) + # LF is internally used as a separator + self.assertFalse(rule.match_substr(None, [['foo', 'bar']], None, ['foo\nbar'], None)) + +class TestFirstComponentMatchingRule(unittest.TestCase): + def test_equal(self): + class FirstCompontentIntegerValue: + def __init__(self, integer): + self.first_component_integer = integer + rule = matching_rules.integerFirstComponentMatch + self.assertTrue(rule.match_equal(None, [FirstCompontentIntegerValue(0), FirstCompontentIntegerValue(1)], 0)) + self.assertTrue(rule.match_equal(None, [FirstCompontentIntegerValue(0), FirstCompontentIntegerValue(1)], 1)) + self.assertFalse(rule.match_equal(None, [FirstCompontentIntegerValue(0), FirstCompontentIntegerValue(1)], 3)) + self.assertFalse(rule.match_equal(None, [], 1)) + +class TestOIDMatchingRule(unittest.TestCase): + def test_equal(self): + schema = ldapserver.schema.RFC4519_SCHEMA + rule = matching_rules.objectIdentifierMatch + self.assertTrue(rule.match_equal(schema, ['person', '2.5.6.2'], '2.5.6.6')) + self.assertTrue(rule.match_equal(schema, ['person', '2.5.6.2'], 'Country')) + self.assertFalse(rule.match_equal(schema, [], '2.5.6.6')) + self.assertFalse(rule.match_equal(schema, [], 'Country')) + self.assertTrue(rule.match_equal(schema, ['person', 'foobar'], 'person')) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + rule.match_equal(schema, ['person'], 'foobar') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + rule.match_equal(schema, ['person', 'foobar'], 'foobar') + self.assertTrue(rule.match_equal(schema, ['person', '0.1.2.3.4'], '0.1.2.3.4')) diff --git a/tests/test_schema_syntaxes.py b/tests/test_schema_syntaxes.py new file mode 100644 index 0000000000000000000000000000000000000000..955481f7046d9bdb81d22bd7159260fbededb0ee --- /dev/null +++ b/tests/test_schema_syntaxes.py @@ -0,0 +1,171 @@ +import unittest +import datetime + +import ldapserver +from ldapserver.schema import syntaxes + +class TestBytesSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.OctetString + self.assertEqual(syntax.encode(None, b'Foo'), b'Foo') + + def test_decode(self): + syntax = syntaxes.OctetString + self.assertEqual(syntax.decode(None, b'Foo'), b'Foo') + +class TestStringSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.DirectoryString + self.assertEqual(syntax.encode(None, 'Foo'), b'Foo') + self.assertEqual(syntax.encode(None, 'äöü'), b'\xc3\xa4\xc3\xb6\xc3\xbc') + + def test_decode(self): + syntax = syntaxes.DirectoryString + self.assertEqual(syntax.decode(None, b'Foo'), 'Foo') + self.assertEqual(syntax.decode(None, b'\xc3\xa4\xc3\xb6\xc3\xbc'), 'äöü') + syntax = syntaxes.IA5String + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'\xc3\xa4\xc3\xb6\xc3\xbc') + # Test regex matching + syntax = syntaxes.BitString + self.assertEqual(syntax.decode(None, b"''B"), "''B") + self.assertEqual(syntax.decode(None, b"'0'B"), "'0'B") + self.assertEqual(syntax.decode(None, b"'010101'B"), "'010101'B") + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"") + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"'0'") + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"'0'b") + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"'0123'B") + +class TestIntegerSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.INTEGER + self.assertEqual(syntax.encode(None, 0), b'0') + self.assertEqual(syntax.encode(None, 1234), b'1234') + self.assertEqual(syntax.encode(None, -1234), b'-1234') + + def test_decode(self): + syntax = syntaxes.INTEGER + self.assertEqual(syntax.decode(None, b'0'), 0) + self.assertEqual(syntax.decode(None, b'1234'), 1234) + self.assertEqual(syntax.decode(None, b'-1234'), -1234) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'-0') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'+1') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'0123') + +class TestSchemaElementSyntaxDefinition(unittest.TestCase): + def test_encode(self): + class SchemaElement: + def __str__(self): + return '( SCHEMA ELEMENT )' + syntax = syntaxes.SchemaElementSyntaxDefinition('1.2.3.4') + self.assertEqual(syntax.encode(None, SchemaElement()), b'( SCHEMA ELEMENT )') + +class TestBooleanSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.Boolean + self.assertEqual(syntax.encode(None, True), b'TRUE') + self.assertEqual(syntax.encode(None, False), b'FALSE') + + def test_decode(self): + syntax = syntaxes.Boolean + self.assertEqual(syntax.decode(None, b'TRUE'), True) + self.assertEqual(syntax.decode(None, b'FALSE'), False) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'true') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'') + +class TestDNSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.DN + self.assertEqual(syntax.encode(None, ldapserver.dn.DN(cn='foobar')), b'cn=foobar') + + def test_decode(self): + syntax = syntaxes.DN + self.assertEqual(syntax.decode(None, b'cn=foobar'), ldapserver.dn.DN(cn='foobar')) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'cn=foobar,,,') + +class TestNameAndOptionalUIDSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.NameAndOptionalUID + self.assertEqual(syntax.encode(None, ldapserver.dn.DN(cn='foobar')), b'cn=foobar') + self.assertEqual(syntax.encode(None, ldapserver.dn.DNWithUID(ldapserver.dn.DN(cn='foobar'), "'0101'B")), b"cn=foobar#'0101'B") + + def test_decode(self): + syntax = syntaxes.NameAndOptionalUID + self.assertEqual(syntax.decode(None, b'cn=foobar'), ldapserver.dn.DN(cn='foobar')) + self.assertEqual(syntax.decode(None, b"cn=foobar#'0101'B"), ldapserver.dn.DNWithUID(ldapserver.dn.DN(cn='foobar'), "'0101'B")) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'cn=foobar,,,') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"cn=foobar,,,#'0101'B") + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b"cn=foobar#'0102'B") + +class TestGeneralizedTimeSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.GeneralizedTime + self.assertEqual(syntax.encode(None, datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)), + b'199412161032Z') + self.assertEqual(syntax.encode(None, datetime.datetime(1994, 12, 16, 5, 32, tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))), + b'199412160532-0500') + + def test_decode(self): + syntax = syntaxes.GeneralizedTime + self.assertEqual(syntax.decode(None, b'199412161032Z'), + datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)) + self.assertEqual(syntax.decode(None, b'199412160532-0500'), + datetime.datetime(1994, 12, 16, 5, 32, tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))) + +class TestPostalAddressSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.PostalAddress + self.assertEqual(syntax.encode(None, ['1234 Main St.', 'Anytown, CA 12345', 'USA']), + b'1234 Main St.$Anytown, CA 12345$USA') + self.assertEqual(syntax.encode(None, ['$1,000,000 Sweepstakes', 'PO Box 1000000', 'Anytown, CA 12345', 'USA']), + b'\\241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA') + + def test_decode(self): + syntax = syntaxes.PostalAddress + self.assertEqual(syntax.decode(None, b'1234 Main St.$Anytown, CA 12345$USA'), + ['1234 Main St.', 'Anytown, CA 12345', 'USA']) + self.assertEqual(syntax.decode(None, b'\\241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA'), + ['$1,000,000 Sweepstakes', 'PO Box 1000000', 'Anytown, CA 12345', 'USA']) + +class TestSubstringAssertionSyntaxDefinition(unittest.TestCase): + def test_decode(self): + syntax = syntaxes.SubstringAssertion + self.assertEqual(syntax.decode(None, b'*foo*'), (None, ['foo'], None)) + self.assertEqual(syntax.decode(None, b'*foo*bar*'), (None, ['foo', 'bar'], None)) + self.assertEqual(syntax.decode(None, b'a*foo*bar*b'), ('a', ['foo', 'bar'], 'b')) + self.assertEqual(syntax.decode(None, b'a*b'), ('a', [], 'b')) + self.assertEqual(syntax.decode(None, b' a\\2A*\\2Afoo*\\5Cbar*\\2Ab'), (' a*', ['*foo', '\\bar'], '*b')) + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'') + with self.assertRaises(ldapserver.exceptions.LDAPInvalidAttributeSyntax): + syntax.decode(None, b'foo') + +class TestUTCTimeSyntaxDefinition(unittest.TestCase): + def test_encode(self): + syntax = syntaxes.UTCTime + self.assertEqual(syntax.encode(None, datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)), + b'9412161032Z') + self.assertEqual(syntax.encode(None, datetime.datetime(1994, 12, 16, 5, 32, tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))), + b'9412160532-0500') + + def test_decode(self): + syntax = syntaxes.UTCTime + self.assertEqual(syntax.decode(None, b'9412161032Z'), + datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)) + self.assertEqual(syntax.decode(None, b'9412160532-0500'), + datetime.datetime(1994, 12, 16, 5, 32, tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))) + + diff --git a/tests/test_server.py b/tests/test_server.py index 639b5de88da989d244c63eeef8e3fa4decc24405..a471fcf0759a9bc4b7644fcdb6cfc947b7f35ab6 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -139,3 +139,33 @@ class TestLDAPRequestHandler(unittest.TestCase): self.assertEqual(resps[0].protocolOp.resultCode, ldap.LDAPResultCode.success) resps = list(handler.handle_message(ldap.ShallowLDAPMessage.from_ber(b'0\x05\x02\x01\x03B\x00')[0])) self.assertEqual(len(resps), 0) + + def test_search(self): + class MockObject: + def __init__(_self, search_result=None): + _self.search_result = search_result + + def search(_self, base_obj, scope, filter_obj, attributes, types_only): + self.assertEqual(base_obj, 'cn=Test,dc=example,dc=com') + self.assertEqual(scope, ldap.SearchScope.singleLevel) + self.assertEqual(ldap.Filter.to_ber(filter_obj), ldap.Filter.to_ber(ldap.FilterPresent('foobar'))) + return _self.search_result + + class RequestHandler(LDAPRequestHandler): + def handle(self): + pass + + def do_search(_self, base_obj, scope, filter_obj): + self.assertEqual(base_obj, 'cn=Test,dc=example,dc=com') + self.assertEqual(scope, ldap.SearchScope.singleLevel) + self.assertEqual(ldap.Filter.to_ber(filter_obj), ldap.Filter.to_ber(ldap.FilterPresent('foobar'))) + yield MockObject(ldap.SearchResultEntry('cn=Test1,dc=example,dc=com')) + yield MockObject(None) + yield MockObject(ldap.SearchResultEntry('cn=Test2,dc=example,dc=com')) + + handler = RequestHandler(None, None, None) + resps = list(handler.handle_search(ldap.SearchRequest('cn=Test,dc=example,dc=com', ldap.SearchScope.singleLevel, filter=ldap.FilterPresent('foobar')), [])) + self.assertEqual(len(resps), 3) + self.assertEqual(ldap.ProtocolOp.to_ber(resps[0]), ldap.ProtocolOp.to_ber(ldap.SearchResultEntry('cn=Test1,dc=example,dc=com'))) + self.assertEqual(ldap.ProtocolOp.to_ber(resps[1]), ldap.ProtocolOp.to_ber(ldap.SearchResultEntry('cn=Test2,dc=example,dc=com'))) + self.assertEqual(ldap.ProtocolOp.to_ber(resps[2]), ldap.ProtocolOp.to_ber(ldap.SearchResultDone())) diff --git a/tests/test_stringprep.py b/tests/test_stringprep.py new file mode 100644 index 0000000000000000000000000000000000000000..43b2612ed87d51edad5a56abe970a778e9a15222 --- /dev/null +++ b/tests/test_stringprep.py @@ -0,0 +1,21 @@ +import unittest +import enum + +from ldapserver.rfc4518_stringprep import prepare, MatchingType, SubstringType + +class TestStringprep(unittest.TestCase): + def test_map(self): + self.assertEqual(prepare('foo\n\rbar', MatchingType.EXACT_STRING, SubstringType.NONE), ' foo bar ') + self.assertEqual(prepare('foo\u200Bbar', MatchingType.EXACT_STRING, SubstringType.NONE), ' foobar ') + + # TODO: systematic test cases + + def test_examples(self): + # Examples from RFC4518 for "Insignificant Character Handling" + self.assertEqual(prepare('foo bar ', MatchingType.EXACT_STRING, SubstringType.NONE), ' foo bar ') + self.assertEqual(prepare('foo bar ', MatchingType.EXACT_STRING, SubstringType.INITIAL), ' foo bar ') + self.assertEqual(prepare('foo bar ', MatchingType.EXACT_STRING, SubstringType.ANY), 'foo bar ') + self.assertEqual(prepare(' 123 456 ', MatchingType.NUMERIC_STRING), '123456') + self.assertEqual(prepare(' ', MatchingType.NUMERIC_STRING), '') + self.assertEqual(prepare(' -123 456 -', MatchingType.TELEPHONE_NUMBER), '123456') + self.assertEqual(prepare('---', MatchingType.TELEPHONE_NUMBER), '')