From 607bf2ddd3474575e598bcc778e65375fb41ca95 Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@jrother.eu> Date: Thu, 25 Nov 2021 22:43:01 +0100 Subject: [PATCH] Entry naming cleanup (Object -> ObjectEntry) --- docs/api.rst | 9 ++- docs/conf.py | 1 - examples/passwd.py | 6 +- ldapserver/__init__.py | 2 +- ldapserver/{objects.py => entries.py} | 75 +++++++++++------- ldapserver/server.py | 32 ++++---- tests/{test_objects.py => test_entries.py} | 92 +++++++++++----------- 7 files changed, 116 insertions(+), 101 deletions(-) rename ldapserver/{objects.py => entries.py} (90%) rename tests/{test_objects.py => test_entries.py} (90%) diff --git a/docs/api.rst b/docs/api.rst index 6aeda77..3284158 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,13 +29,16 @@ Distinguished Names :members: :special-members: __str__ -Objects +Entries ------- +An LDAP server provides access to a tree of directory entries (the DIT). +There are different kinds of entries: Object entries holding actual data objects (see :class:`ObjectEntry`), alias entries providing alternative naming for other entries (not supported) and subentries holding administrative/operational information (see :class:`SubschemaSubentry` and :class:`RootDSE`). + .. autoclass:: ldapserver.AttributeDict :members: -.. autoclass:: ldapserver.Object +.. autoclass:: ldapserver.Entry :members: .. autoclass:: ldapserver.RootDSE @@ -47,7 +50,7 @@ Objects .. autodata:: ldapserver.WILDCARD_VALUE :no-value: -.. autoclass:: ldapserver.ObjectTemplate +.. autoclass:: ldapserver.EntryTemplate :members: Schema diff --git a/docs/conf.py b/docs/conf.py index 74e1d91..066df16 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,7 +57,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, diff --git a/examples/passwd.py b/examples/passwd.py index 8e746cf..ce024a3 100644 --- a/examples/passwd.py +++ b/examples/passwd.py @@ -12,14 +12,14 @@ class RequestHandler(ldapserver.LDAPRequestHandler): def do_search(self, basedn, scope, filterobj): yield from super().do_search(basedn, scope, filterobj) - yield self.subschema.Object('dc=example,dc=com', **{ + yield self.subschema.ObjectEntry('dc=example,dc=com', **{ 'objectClass': ['top', 'dcObject', 'organization'], 'structuralObjectClass': ['organization'], }) user_gids = {} for user in pwd.getpwall(): user_gids[user.pw_gid] = user_gids.get(user.pw_gid, set()) | {user.pw_name} - yield self.subschema.Object(self.subschema.DN('ou=users,dc=example,dc=com', uid=user.pw_name), **{ + yield self.subschema.ObjectEntry(self.subschema.DN('ou=users,dc=example,dc=com', uid=user.pw_name), **{ 'objectClass': ['top', 'organizationalperson', 'person', 'posixaccount'], 'structuralObjectClass': ['organizationalperson'], 'uid': [user.pw_name], @@ -29,7 +29,7 @@ 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(self.subschema.DN('ou=groups,dc=example,dc=com', cn=group.gr_name), **{ + yield self.subschema.ObjectEntry(self.subschema.DN('ou=groups,dc=example,dc=com', cn=group.gr_name), **{ 'objectClass': ['top', 'groupOfUniqueNames', 'posixGroup'], 'structuralObjectClass': ['groupOfUniqueNames'], 'cn': [group.gr_name], diff --git a/ldapserver/__init__.py b/ldapserver/__init__.py index c749ca5..ceeec72 100644 --- a/ldapserver/__init__.py +++ b/ldapserver/__init__.py @@ -4,5 +4,5 @@ from . import schema from . import rfc4518_stringprep from .dn import * -from .objects import * +from .entries import * from .server import * diff --git a/ldapserver/objects.py b/ldapserver/entries.py similarity index 90% rename from ldapserver/objects.py rename to ldapserver/entries.py index a8c0a55..5a6c931 100644 --- a/ldapserver/objects.py +++ b/ldapserver/entries.py @@ -4,7 +4,7 @@ import enum from . import ldap, exceptions from .dn import DN, RDN, RDNAssertion -__all__ = ['AttributeDict', 'Object', 'RootDSE', 'WILDCARD_VALUE', 'ObjectTemplate', 'SubschemaSubentry'] +__all__ = ['AttributeDict', 'Entry', 'ObjectEntry', 'RootDSE', 'WILDCARD_VALUE', 'EntryTemplate', 'SubschemaSubentry'] class TypeKeysView(collections.abc.Set): def __init__(self, attributes): @@ -110,10 +110,18 @@ class FilterResult(enum.Enum): FALSE = enum.auto() UNDEFINED = enum.auto() -class Object(AttributeDict): +class Entry(AttributeDict): + '''Base class for all directory entries + + All entries implement the methods :any:`Entry.search` and + :any:`Entry.compare`. :class:`LDAPRequestHandler` uses them to process the + corresponding LDAP requests. + + Entries also provide dict-like access to attributes. See + :class:`AttributeDict`.''' def __init__(self, schema, dn, **attributes): super().__init__(schema, **attributes) - #: Objects distinguished name (:class:`DN`) + #: Entry's distinguished name (:class:`DN`) self.dn = DN(schema, dn) def __search_match_dn(self, basedn, scope): @@ -220,10 +228,10 @@ class Object(AttributeDict): self.__search_match_filter(filter_obj) == FilterResult.TRUE def search(self, base_obj, scope, filter_obj, attributes, types_only): - '''Return SEARCH result for the object if it matches the operation + '''Return SEARCH result for the entry if it matches the operation parameters - :param base_obj: DN of the base object + :param base_obj: DN of the base entry :type base_obj: str :param scope: Scope of base_obj :type scope: ldap.SearchScope @@ -233,7 +241,7 @@ class Object(AttributeDict): :type attributes: list of str :param types_only: Omit values in :class:`ldap.PartialAttribute` :type types_only: bool - :returns: SEARCH result for the object if it matches the operation + :returns: SEARCH result for the entry if it matches the operation parameters, None otherwise :rtype: ldap.SearchResultEntry or None @@ -259,15 +267,15 @@ class Object(AttributeDict): return ldap.SearchResultEntry(str(self.dn), partial_attributes) def compare(self, dn, attribute, value): - '''Return the result of the COMPARE operation applied to the object + '''Return the result of the COMPARE operation applied to the entry - :param dn: DN of the object to be compared + :param dn: DN of the entry to be compared :type dn: str :param attribute: Attribute OID or short descriptive name :type attribute: str :param value: Assertion value :type value: bytes - :raises exceptions.LDAPNoSuchObject: if dn does not refer to the object + :raises exceptions.LDAPNoSuchObject: if dn does not refer to the entry :raises exceptions.LDAPError: if operation results to anything other than TRUE/FALSE :return: True/False if COMPARE operation evaluates to TRUE/FALSE @@ -287,7 +295,14 @@ class Object(AttributeDict): raise exceptions.LDAPUndefinedAttributeType() from exc return attribute_type.match_equal(self.get(attribute_type, subtypes=True), value) -class RootDSE(Object): +class ObjectEntry(Entry): + '''Regular object entry''' + +class RootDSE(Entry): + '''Root DSA-specific (server-specific) Entry + + Root of the Directory Information Tree (DIT). It's always identified by the + emtpy DN. Provides information about the server, like supported features.''' def __init__(self, schema, **attributes): super().__init__(schema, DN(schema), **attributes) self.setdefault('objectClass', ['top']) @@ -300,7 +315,7 @@ class RootDSE(Object): class WildcardValue: pass -#: Special wildcard value for :class:`ObjectTemplate` +#: Special wildcard value for :class:`EntryTemplate` WILDCARD_VALUE = WildcardValue() class TemplateFilterResult(enum.Enum): @@ -309,7 +324,7 @@ class TemplateFilterResult(enum.Enum): UNDEFINED = enum.auto() MAYBE_TRUE = enum.auto() -class ObjectTemplate(AttributeDict): +class EntryTemplate(AttributeDict): def __init__(self, schema, parent_dn, rdn_attribute, **attributes): super().__init__(schema, **attributes) self.parent_dn = DN(schema, parent_dn) @@ -332,7 +347,7 @@ class ObjectTemplate(AttributeDict): return False, AttributeDict(self.schema) def __search_match_dn(self, basedn, scope): - '''Return whether objects from this template might match the provided parameters''' + '''Return whether entries from this template might match the provided parameters''' return self.__match_extract_dn_constraints(basedn, scope)[0] def __extract_dn_constraints(self, basedn, scope): @@ -467,15 +482,15 @@ class ObjectTemplate(AttributeDict): 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 whether entries based on this template might match the search parameters - :param base_obj: DN of the base object + :param base_obj: DN of the base entry :type base_obj: str :param scope: Scope of base_obj :type scope: ldap.SearchScope :param filter_obj: Search filter :type filter_obj: ldap.Filter - :returns: True if objects based on this template might match the SEARCH + :returns: True if entries based on this template might match the SEARCH parameters or False if they do not match :rtype: bool''' return self.__search_match_dn(DN.from_str(self.schema, base_obj), scope) and \ @@ -483,21 +498,21 @@ class ObjectTemplate(AttributeDict): TemplateFilterResult.MAYBE_TRUE) def extract_search_constraints(self, base_obj, scope, filter_obj): - '''Return approximate value constraints for objects that match SEARCH parameters + '''Return approximate value constraints for entries that match SEARCH parameters - :returns: :class:`AttributeDict` with values that any object must have + :returns: :class:`AttributeDict` with values that any entry must have to match potentially match SEARCH parameters :rtype: AttributeDict Example: >>> subschema = SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema') - >>> template = subschema.ObjectTemplate('ou=users,dc=example,dc=com', 'cn', objectclass=['person', 'top'], sn=[WILDCARD_VALUE]) + >>> template = subschema.EntryTemplate('ou=users,dc=example,dc=com', 'cn', objectclass=['person', 'top'], sn=[WILDCARD_VALUE]) >>> template.extract_search_constraints('cn=test,ou=users,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterEqual('sn', b'foobar')) subschema.AttributeDict(cn=['test'], sn=['foobar']) Note that the results are an approximation (i.e. some constrains may be - missing) and the quality may improve over time. Also note that every object + missing) and the quality may improve over time. Also note that every entry that matches the SEARCH parameters also matches the constrains, but not vice versa.''' constraints = self.__extract_filter_constraints(filter_obj) @@ -505,17 +520,17 @@ class ObjectTemplate(AttributeDict): constraints[key] += values return constraints - def create_object(self, rdn_value, **attributes): - '''Instanciate :class:`Object` based on template + def create_entry(self, rdn_value, **attributes): + '''Instanciate :class:`Entry` based on template :param rdn_value: RDN value for DN construction :type rdn_value: any - :rtype: Object + :rtype: Entry Template attributes set to :any:`WILDCARD_VALUE` are stripped. Only template attributes set to :any:`WILDCARD_VALUE` may be overwritten with `attributes`.''' - obj = Object(self.schema, DN(self.schema, self.parent_dn, **{self.rdn_attribute: rdn_value})) + obj = Entry(self.schema, DN(self.schema, self.parent_dn, **{self.rdn_attribute: rdn_value})) for key, values in attributes.items(): if WILDCARD_VALUE not in self[key]: raise ValueError(f'Cannot set attribute "{key}" that is not set to [WILDCARD_VALUE] in the template') @@ -525,8 +540,8 @@ class ObjectTemplate(AttributeDict): obj[attribute_type] = values return obj -class SubschemaSubentry(Object): - '''Special :class:`Object` providing information on a Schema''' +class SubschemaSubentry(Entry): + '''Subentry providing information on a :class:`Schema`''' def __init__(self, schema, dn, **attributes): super().__init__(schema, dn, **attributes) self['subschemaSubentry'] = [self.dn] @@ -540,12 +555,12 @@ class SubschemaSubentry(Object): # pylint: disable=invalid-name #: Shorthand for :class:`AttributeDict` self.AttributeDict = lambda **attributes: AttributeDict(schema, **attributes) - #: Shorthand for :class:`Object` - self.Object = lambda *args, **attributes: Object(schema, *args, subschemaSubentry=[self.dn], **attributes) + #: Shorthand for :class:`ObjectEntry` + self.ObjectEntry = lambda *args, **attributes: ObjectEntry(schema, *args, subschemaSubentry=[self.dn], **attributes) #: Shorthand for :class:`RootDSE` self.RootDSE = lambda **attributes: RootDSE(schema, subschemaSubentry=[self.dn], **attributes) - #: Shorthand for :class:`ObjectTemplate` - self.ObjectTemplate = lambda *args, **kwargs: ObjectTemplate(schema, *args, subschemaSubentry=[self.dn], **kwargs) + #: Shorthand for :class:`EntryTemplate` + self.EntryTemplate = lambda *args, **kwargs: EntryTemplate(schema, *args, subschemaSubentry=[self.dn], **kwargs) class Wrapper: def __init__(self, cls, schema): self.cls = cls diff --git a/ldapserver/server.py b/ldapserver/server.py index 1c58592..f9923cc 100644 --- a/ldapserver/server.py +++ b/ldapserver/server.py @@ -8,7 +8,7 @@ import random import string import itertools -from . import asn1, exceptions, ldap, schema, objects +from . import asn1, exceptions, ldap, schema, entries __all__ = ['BaseLDAPRequestHandler', 'LDAPRequestHandler'] @@ -168,7 +168,7 @@ class BaseLDAPRequestHandler(socketserver.BaseRequestHandler): class LDAPRequestHandler(BaseLDAPRequestHandler): #: :class:`SubschemaSubentry` object that describes the schema. Default #: value uses :any:`schema.RFC4519_SCHEMA`. Returned by :any:`do_search`. - subschema = objects.SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema', cn=['Subschema']) + subschema = entries.SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema', cn=['Subschema']) #: :class:`RootDSE` object containing information about the server, such #: as supported extentions and SASL authentication mechansims. Content is @@ -437,11 +437,11 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): yield ldap.SearchResultDone(ldap.LDAPResultCode.success), [build_control()] return is_last = True - entries = 0 + result_count = 0 time_start = time.perf_counter() for entry, is_last in itertools.islice(iterator, 0, paged_control.size): self.logger.debug('SEARCH entry %r', entry) - entries += 1 + result_count += 1 yield entry cookie = b'' if not is_last: @@ -450,8 +450,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): self.__paged_searches[cookie] = iterator, op yield ldap.SearchResultDone(ldap.LDAPResultCode.success), [build_control(cookie=cookie)] time_end = time.perf_counter() - self.logger.info('SEARCH dn=%r dn_scope=%s filter=%s attributes=%r page_cookie=%r entries=%d duration_seconds=%.3f', - op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), cookie, entries, time_end - time_start) + self.logger.info('SEARCH dn=%r dn_scope=%s filter=%s attributes=%r page_cookie=%r result_count=%d duration_seconds=%.3f', + op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), cookie, result_count, time_end - time_start) def handle_search(self, op, controls=None): self.logger.debug('SEARCH request dn=%r dn_scope=%s filter=%r attributes=%r', @@ -463,18 +463,18 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): if paged_control: yield from self.__handle_search_paged(op, paged_control, controls) return - entries = 0 + result_count = 0 time_start = time.perf_counter() for obj in self.do_search(op.baseObject, op.scope, op.filter): entry = obj.search(op.baseObject, op.scope, op.filter, op.attributes, op.typesOnly) if entry: self.logger.debug('SEARCH entry %r', entry) - entries += 1 + result_count += 1 yield entry yield ldap.SearchResultDone(ldap.LDAPResultCode.success) time_end = time.perf_counter() - self.logger.info('SEARCH dn=%r dn_scope=%s filter=\'%s\' attributes=%r entries=%d duration_seconds=%.3f', - op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), entries, time_end - time_start) + self.logger.info('SEARCH dn=%r dn_scope=%s filter=\'%s\' attributes=%r result_count=%d duration_seconds=%.3f', + op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), result_count, time_end - time_start) def do_search(self, baseobj, scope, filterobj): '''Return result candidates for a SEARCH operation @@ -487,16 +487,16 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): :param filterobj: Filter object :type filterobj: ldap.Filter :raises exceptions.LDAPError: on error - :returns: All LDAP objects that might match the parameters of the SEARCH + :returns: All entries that might match the parameters of the SEARCH operation. - :rtype: Iterable of :class:`Object` + :rtype: Iterable of :class:`Entry` The default implementation yields :any:`rootdse` and :any:`subschema`. Both are importent for feature detection, so make sure to also return them (e.g. with ``yield from super().do_search(...)``). - For every returned object :any:`Object.search` is called to filter out - non-matching objects and to construct the response. + For every returned object :any:`Entry.search` is called to filter out + non-matching entries and to construct the response. Note that if this method is as an iterator, its execution may be paused for extended periods of time or aborted prematurly.''' @@ -525,10 +525,10 @@ class LDAPRequestHandler(BaseLDAPRequestHandler): :param value: Attribute value :type value: bytes :raises exceptions.LDAPError: on error - :returns: `Object` or None + :returns: `Entry` or None The default implementation calls `do_search` and returns the first object - for which :any:`Object.compare` does not raise + for which :any:`Entry.compare` does not raise :any:`exceptions.LDAPNoSuchObject` (i.e. the object has the requested DN).''' objs = self.do_search(dn, ldap.SearchScope.baseObject, ldap.FilterPresent(attribute='objectClass')) for obj in objs: diff --git a/tests/test_objects.py b/tests/test_entries.py similarity index 90% rename from tests/test_objects.py rename to tests/test_entries.py index cf29d6e..e1d7529 100644 --- a/tests/test_objects.py +++ b/tests/test_entries.py @@ -2,9 +2,7 @@ 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 +from ldapserver import DN, AttributeDict, ObjectEntry, RootDSE, SubschemaSubentry, EntryTemplate, WILDCARD_VALUE, ldap schema = ldapserver.schema.RFC4519_SCHEMA @@ -95,15 +93,15 @@ class TestAttributeDict(unittest.TestCase): self.assertEqual(attrs.setdefault('c', ['default']), ['default']) self.assertEqual(attrs['c'], ['default']) -class TestObject(unittest.TestCase): +class TestObjectEntry(unittest.TestCase): def test_init(self): - obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[]) + obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[]) self.assertEqual(obj.dn, DN.from_str(schema, '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']) + obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', objectclass=['top']) true_filter = ldap.FilterPresent('objectClass') scope = ldap.SearchScope.baseObject @@ -128,7 +126,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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 @@ -141,7 +139,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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 @@ -154,7 +152,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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') @@ -187,7 +185,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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') @@ -220,7 +218,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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 @@ -243,7 +241,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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')]))) @@ -251,7 +249,7 @@ class TestObject(unittest.TestCase): 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)]) + obj = ObjectEntry(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'))) @@ -261,7 +259,7 @@ class TestObject(unittest.TestCase): 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)]) + obj = ObjectEntry(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'))) @@ -272,7 +270,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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 @@ -295,7 +293,7 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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 @@ -314,10 +312,10 @@ class TestObject(unittest.TestCase): 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']) + obj = ObjectEntry(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']) + obj = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -338,17 +336,17 @@ class TestObject(unittest.TestCase): self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False)))) def test_search(self): - class TrueObject(Object): + class TrueEntry(ObjectEntry): def match_search(self, base_obj, scope, filter_obj): return True - class FalseObject(Object): + class FalseEntry(ObjectEntry): 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(schema, 'cn=subschema')]) + obj = FalseEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, '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(schema, 'cn=subschema')]) + obj = TrueEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, '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) @@ -374,7 +372,7 @@ class TestObject(unittest.TestCase): {'cn': [], 'subschemaSubentry': []}) def test_compare(self): - obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top']) + obj = ObjectEntry(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): @@ -400,12 +398,12 @@ class TestRootDSE(unittest.TestCase): 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): +class TestEntryTemplate(unittest.TestCase): def test_init(self): - obj = ObjectTemplate(schema, 'ou=users,dc=example,dc=com', 'uid', cn=['foo', 'bar'], uid=[]) + obj = EntryTemplate(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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) true_filter = ldap.FilterPresent('objectClass') scope = ldap.SearchScope.baseObject @@ -427,7 +425,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -441,7 +439,7 @@ class TestObjectTemplate(unittest.TestCase): # 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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # Not True = False @@ -454,7 +452,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree true = ldap.FilterPresent('objectclass') @@ -501,7 +499,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree true = ldap.FilterPresent('objectclass') @@ -548,7 +546,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -575,7 +573,7 @@ class TestObjectTemplate(unittest.TestCase): 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']) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar', 'test']) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -596,7 +594,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(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 @@ -621,7 +619,7 @@ class TestObjectTemplate(unittest.TestCase): 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]) + template = EntryTemplate(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 @@ -646,7 +644,7 @@ class TestObjectTemplate(unittest.TestCase): 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']) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -673,7 +671,7 @@ class TestObjectTemplate(unittest.TestCase): 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']) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -696,7 +694,7 @@ class TestObjectTemplate(unittest.TestCase): 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']) + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar']) dn = 'dc=example,dc=com' scope = ldap.SearchScope.wholeSubtree # True @@ -721,21 +719,21 @@ class TestObjectTemplate(unittest.TestCase): 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']) + template = EntryTemplate(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']) + def test_create_entry(self): + template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], c=[WILDCARD_VALUE], uid=['foobar']) + obj = template.create_entry('foo', cn=['foo', 'bar'], c=['DE']) self.assertEqual(obj.dn, DN(schema, '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']) + obj = template.create_entry('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']) + template.create_entry('foo', cn=['foo', 'bar'], c=['DE'], description=['foo bar']) class TestSubschemaSubentry(unittest.TestCase): def test_init(self): @@ -756,8 +754,8 @@ class TestSubschemaSubentry(unittest.TestCase): 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) + obj = subschema.ObjectEntry('cn=foo,dc=example,dc=com', cn=['foo']) + self.assertIsInstance(obj, ObjectEntry) self.assertIs(obj.schema, subschema.schema) self.assertEqual(obj.dn, DN(schema, 'cn=foo,dc=example,dc=com')) self.assertEqual(obj['cn'], ['foo']) @@ -766,7 +764,7 @@ class TestSubschemaSubentry(unittest.TestCase): 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) + template = subschema.EntryTemplate('dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE]) + self.assertIsInstance(template, EntryTemplate) self.assertIs(template.schema, subschema.schema) self.assertEqual(template['subschemaSubentry'], [DN(schema, 'cn=Subschema')]) -- GitLab