Merge branch 'python-ldap' into 'master'

Port python test suite to python-ldap

See merge request openldap/openldap!732
This commit is contained in:
Ondřej Kuzník 2026-02-12 00:16:15 +00:00
commit 8b0bdec6da
4 changed files with 69 additions and 44 deletions

View file

@ -20,7 +20,7 @@
OpenLDAP fixtures for backends OpenLDAP fixtures for backends
""" """
import ldap0 import ldap
import logging import logging
import os import os
import pathlib import pathlib
@ -28,7 +28,7 @@ import pytest
import secrets import secrets
import tempfile import tempfile
from ldap0.controls.readentry import PostReadControl from ldap.controls.readentry import PostReadControl
from .slapd import server from .slapd import server
@ -36,6 +36,7 @@ from .slapd import server
SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute() SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute()
BUILDROOT = pathlib.Path(os.environ.get('TOP_BUILDDIR', SOURCEROOT)).absolute() BUILDROOT = pathlib.Path(os.environ.get('TOP_BUILDDIR', SOURCEROOT)).absolute()
NOTSET = object()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,10 +44,17 @@ logger = logging.getLogger(__name__)
class Database: class Database:
have_directory = True have_directory = True
def __init__(self, server, suffix, backend): def __init__(self, server, suffix, backend, *,
rootdn=NOTSET, module=NOTSET):
if rootdn is NOTSET:
rootdn = suffix
if module is NOTSET:
module = (BUILDROOT/"servers"/"slapd"/
f"back-{backend}"/f"back_{backend}")
self.server = server self.server = server
self.suffix = suffix self.suffix = suffix
self.rootdn = suffix self.rootdn = rootdn
self.secret = secrets.token_urlsafe() self.secret = secrets.token_urlsafe()
self.overlays = [] self.overlays = []
@ -54,18 +62,21 @@ class Database:
raise RuntimeError(f"Suffix {suffix} already configured in server") raise RuntimeError(f"Suffix {suffix} already configured in server")
if self.have_directory: if self.have_directory:
self.directory = tempfile.TemporaryDirectory(dir=server.home) self.directory = tempfile.TemporaryDirectory(dir=server.home, delete=False)
if module:
server.load_module(module)
conn = server.connect() conn = server.connect()
conn.simple_bind_s("cn=config", server.secret) conn.simple_bind_s("cn=config", server.secret)
# We're just after the generated DN, no other attributes at the moment # We're just after the generated DN, no other attributes at the moment
control = PostReadControl(True, []) control = PostReadControl(True, ["1.1"])
result = conn.add_s( _, _, _, ctrls = conn.add_ext_s(
f"olcDatabase={backend},cn=config", self._entry(), f"olcDatabase={backend},cn=config", list(self._entry().items()),
req_ctrls=[control]) serverctrls=[control])
dn = result.ctrls[0].res.dn_s dn = ctrls[0].dn
self.dn = dn self.dn = dn
server.suffixes[suffix] = self server.suffixes[suffix] = self
@ -74,9 +85,11 @@ class Database:
entry = { entry = {
"objectclass": [self.objectclass.encode()], "objectclass": [self.objectclass.encode()],
"olcSuffix": [self.suffix.encode()], "olcSuffix": [self.suffix.encode()],
"olcRootDN": [self.suffix.encode()],
"olcRootPW": [self.secret.encode()],
} }
if self.rootdn is not None:
entry["olcRootDN"] = [self.rootdn.encode()]
if self.rootdn.endswith(self.suffix):
entry["olcRootPW"] = [self.secret.encode()]
if self.have_directory: if self.have_directory:
entry["olcDbDirectory"] = [self.directory.name.encode()] entry["olcDbDirectory"] = [self.directory.name.encode()]
return entry return entry
@ -92,10 +105,9 @@ class MDB(Database):
super().__init__(server, suffix, "mdb") super().__init__(server, suffix, "mdb")
def _entry(self): def _entry(self):
entry = { return super()._entry() | {
"olcDbMaxSize": [str(self._size).encode()], "olcDbMaxSize": [str(self._size).encode()],
} }
return {**super()._entry(), **entry}
class LDAP(Database): class LDAP(Database):
@ -107,15 +119,24 @@ class LDAP(Database):
super().__init__(server, suffix, "ldap") super().__init__(server, suffix, "ldap")
def _entry(self): def _entry(self):
entry = { return super()._entry() | {
"olcDbURI": [" ".join(self.uris).encode()], "olcDbURI": [" ".join(self.uris).encode()],
} }
return {**super()._entry(), **entry}
class Monitor(Database):
have_directory = False
objectclass = "olcMonitorConfig"
def __init__(self, server):
super().__init__(server, "cn=monitor", "monitor",
rootdn="cn=config", module=None)
backend_types = { backend_types = {
"mdb": MDB, "mdb": MDB,
"ldap": LDAP, "ldap": LDAP,
"monitor": Monitor,
} }

View file

@ -24,7 +24,7 @@ import logging
import os import os
import pathlib import pathlib
from ldap0.controls.readentry import PostReadControl from ldap.controls.readentry import PostReadControl
SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute() SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute()
@ -51,12 +51,13 @@ class Overlay:
server.load_module(overlay) server.load_module(overlay)
# We're just after the generated DN, no other attributes at the moment # We're just after the generated DN, no other attributes at the moment
control = PostReadControl(True, []) control = PostReadControl(True, ["1.1"])
result = conn.add_s( _, _, _, ctrls = conn.add_ext_s(
f"olcOverlay={overlay_name},{database.dn}", self._entry(), f"olcOverlay={overlay_name},{database.dn}",
req_ctrls=[control]) list(self._entry().items()),
self.dn = result.ctrls[0].res.dn_s serverctrls=[control])
self.dn = ctrls[0].dn
if order == -1: if order == -1:
database.overlays.append(self) database.overlays.append(self)

View file

@ -20,7 +20,7 @@
OpenLDAP server fixtures OpenLDAP server fixtures
""" """
import ldap0 import ldap
import ldapurl import ldapurl
import logging import logging
import os import os
@ -34,7 +34,7 @@ import subprocess
import tempfile import tempfile
import textwrap import textwrap
from ldap0.ldapobject import LDAPObject from ldap.ldapobject import LDAPObject
SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute() SOURCEROOT = pathlib.Path(os.environ.get('TOP_SRCDIR', "..")).absolute()
@ -208,12 +208,13 @@ class Server:
conn.simple_bind_s('cn=config', self.secret) conn.simple_bind_s('cn=config', self.secret)
moduleload_object = None moduleload_object = None
for entry in conn.search_s('cn=config', ldap0.SCOPE_SUBTREE, for dn, attrs in conn.search_s('cn=config', ldap.SCOPE_SUBTREE,
'objectclass=olcModuleList', 'objectclass=olcModuleList',
['olcModuleLoad']): ['olcModuleLoad']):
if not moduleload_object: if not moduleload_object:
moduleload_object = entry.dn_s moduleload_object = dn
for value in entry.entry_s.get('olcModuleLoad', []): for value in attrs.get('olcModuleLoad', []):
value = value.decode()
if value[0] == '{': if value[0] == '{':
value = value[value.find('}')+1:] value = value[value.find('}')+1:]
if pathlib.Path(value).stem == module_name: if pathlib.Path(value).stem == module_name:
@ -224,11 +225,11 @@ class Server:
if moduleload_object: if moduleload_object:
conn.modify_s( conn.modify_s(
moduleload_object, moduleload_object,
[(ldap0.MOD_ADD, b'olcModuleLoad', [str(module).encode()])]) [(ldap.MOD_ADD, 'olcModuleLoad', [str(module).encode()])])
else: else:
conn.add_s('cn=module,cn=config', entry = {'objectClass': [b'olcModuleList'],
{'objectClass': [b'olcModuleList'], 'olcModuleLoad': [str(module).encode()]}
'olcModuleLoad': [str(module).encode()]}) conn.add_s('cn=module,cn=config', list(entry.items()))
@property @property
def uri(self): def uri(self):
@ -251,7 +252,7 @@ class ServerManager:
return self.address[1] return self.address[1]
def new_server(self): def new_server(self):
path = tempfile.TemporaryDirectory(dir=self.tmpdir) path = tempfile.TemporaryDirectory(dir=self.tmpdir, delete=False)
return Server(path, self) return Server(path, self)
def wait(self, token): def wait(self, token):
@ -292,4 +293,4 @@ def server(server_factory):
def test_rootdse(server): def test_rootdse(server):
conn = server.connect() conn = server.connect()
conn.search_s("", scope=ldap0.SCOPE_BASE) conn.search_s("", scope=ldap.SCOPE_BASE)

View file

@ -20,7 +20,7 @@
OpenLDAP fixtures for overlays OpenLDAP fixtures for overlays
""" """
import ldap0 import ldap
import logging import logging
import os import os
import pathlib import pathlib
@ -86,7 +86,7 @@ def mmr(request, server_factory):
conn.simple_bind_s("cn=config", server.secret) conn.simple_bind_s("cn=config", server.secret)
conn.modify_s("cn=config", [ conn.modify_s("cn=config", [
(ldap0.MOD_REPLACE, b"olcServerId", [str(serverid).encode()])]) (ldap.MOD_REPLACE, "olcServerId", [str(serverid).encode()])])
server.serverid = serverid server.serverid = serverid
servers[serverid] = server servers[serverid] = server
@ -110,8 +110,8 @@ def mmr(request, server_factory):
f'binddn="{db.suffix}" credentials="{db.secret}"').encode()) f'binddn="{db.suffix}" credentials="{db.secret}"').encode())
connections[serverid].modify_s(db.dn, [ connections[serverid].modify_s(db.dn, [
(ldap0.MOD_REPLACE, b"olcSyncrepl", syncrepl), (ldap.MOD_REPLACE, "olcSyncrepl", syncrepl),
(ldap0.MOD_REPLACE, b"olcMultiprovider", [b"TRUE"])]) (ldap.MOD_REPLACE, "olcMultiprovider", [b"TRUE"])])
yield servers yield servers
@ -142,27 +142,29 @@ def test_mmr(mmr):
conn.simple_bind_s(db.rootdn, db.secret) conn.simple_bind_s(db.rootdn, db.secret)
if not entries_added: if not entries_added:
conn.add_s(suffix, { entry = {
"objectClass": [b"organization", "objectClass": [b"organization",
b"domainRelatedObject", b"domainRelatedObject",
b"dcobject"], b"dcobject"],
"o": [b"Example, Inc."], "o": [b"Example, Inc."],
"associatedDomain": [b"example.com"]}) "associatedDomain": [b"example.com"]}
conn.add_s(suffix, list(entry.items()))
entries_added.add(suffix) entries_added.add(suffix)
# Make sure all hosts have the suffix entry # Make sure all hosts have the suffix entry
wait_for_resync(suffix, mmr.values()) wait_for_resync(suffix, mmr.values())
dn = f"cn=entry{serverid},{suffix}" dn = f"cn=entry{serverid},{suffix}"
conn.add_s(dn, {"objectClass": [b"device"], entry = {"objectClass": [b"device"],
"description": [(f"Entry created on serverid " "description": [(f"Entry created on serverid "
f"{serverid}").encode()]}) f"{serverid}").encode()]}
conn.add_s(dn, list(entry.items()))
entries_added.add(dn) entries_added.add(dn)
connections.append(conn) connections.append(conn)
wait_for_resync(suffix, mmr.values()) wait_for_resync(suffix, mmr.values())
for conn in connections: for conn in connections:
result = conn.search_s(suffix, ldap0.SCOPE_SUBTREE, attrlist=['1.1']) result = conn.search_s(suffix, ldap.SCOPE_SUBTREE, attrlist=['1.1'])
dns = {entry.dn_s for entry in result} dns = {dn for dn, entry in result}
assert dns == entries_added, \ assert dns == entries_added, \
f"Server {serverid} contents do not match expectations" f"Server {serverid} contents do not match expectations"