Skip to content

Commit 7dc1e62

Browse files
authored
SlapdObject directory based configuration method, and slapadd implementation (#382)
#382
1 parent ca684f8 commit 7dc1e62

File tree

4 files changed

+156
-85
lines changed

4 files changed

+156
-85
lines changed

Doc/spelling_wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,10 @@ serverctrls
129129
sessionSourceIp
130130
sessionSourceName
131131
sessionTrackingIdentifier
132+
slapadd
132133
sizelimit
133134
slapd
135+
startup
134136
stderr
135137
stdout
136138
str

Lib/slapdtest/_slapdtest.py

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,33 @@
2323

2424
HERE = os.path.abspath(os.path.dirname(__file__))
2525

26-
# a template string for generating simple slapd.conf file
27-
SLAPD_CONF_TEMPLATE = r"""
28-
serverID %(serverid)s
29-
moduleload back_%(database)s
30-
%(include_directives)s
31-
loglevel %(loglevel)s
32-
allow bind_v2
33-
34-
authz-regexp
35-
"gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth"
36-
"%(rootdn)s"
37-
38-
database %(database)s
39-
directory "%(directory)s"
40-
suffix "%(suffix)s"
41-
rootdn "%(rootdn)s"
42-
rootpw "%(rootpw)s"
43-
44-
TLSCACertificateFile "%(cafile)s"
45-
TLSCertificateFile "%(servercert)s"
46-
TLSCertificateKeyFile "%(serverkey)s"
47-
# ignore missing client cert but fail with invalid client cert
48-
TLSVerifyClient try
49-
50-
authz-regexp
51-
"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)"
52-
"ldap://ou=people,dc=local???($1)"
53-
26+
# a template string for generating simple slapd.d file
27+
SLAPD_CONF_TEMPLATE = r"""dn: cn=config
28+
objectClass: olcGlobal
29+
cn: config
30+
olcServerID: %(serverid)s
31+
olcLogLevel: %(loglevel)s
32+
olcAllows: bind_v2
33+
olcAuthzRegexp: {0}"gidnumber=%(root_gid)s\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" "%(rootdn)s"
34+
olcAuthzRegexp: {1}"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" "ldap://ou=people,dc=local???($1)"
35+
olcTLSCACertificateFile: %(cafile)s
36+
olcTLSCertificateFile: %(servercert)s
37+
olcTLSCertificateKeyFile: %(serverkey)s
38+
olcTLSVerifyClient: try
39+
40+
dn: cn=module,cn=config
41+
objectClass: olcModuleList
42+
cn: module
43+
olcModuleLoad: back_%(database)s
44+
45+
dn: olcDatabase=%(database)s,cn=config
46+
objectClass: olcDatabaseConfig
47+
objectClass: olcMdbConfig
48+
olcDatabase: %(database)s
49+
olcSuffix: %(suffix)s
50+
olcRootDN: %(rootdn)s
51+
olcRootPW: %(rootpw)s
52+
olcDbDirectory: %(directory)s
5453
"""
5554

5655
LOCALHOST = '127.0.0.1'
@@ -175,6 +174,9 @@ class SlapdObject(object):
175174
manager, the slapd server is shut down and the temporary data store is
176175
removed.
177176
177+
:param openldap_schema_files: A list of schema names or schema paths to
178+
load at startup. By default this only contains `core`.
179+
178180
.. versionchanged:: 3.1
179181
180182
Added context manager functionality
@@ -187,10 +189,10 @@ class SlapdObject(object):
187189
slapd_loglevel = 'stats stats2'
188190
local_host = LOCALHOST
189191
testrunsubdirs = (
190-
'schema',
192+
'slapd.d',
191193
)
192194
openldap_schema_files = (
193-
'core.schema',
195+
'core.ldif',
194196
)
195197

196198
TMPDIR = os.environ.get('TMP', os.getcwd())
@@ -217,8 +219,7 @@ def __init__(self):
217219
self._port = self._avail_tcp_port()
218220
self.server_id = self._port % 4096
219221
self.testrundir = os.path.join(self.TMPDIR, 'python-ldap-test-%d' % self._port)
220-
self._schema_prefix = os.path.join(self.testrundir, 'schema')
221-
self._slapd_conf = os.path.join(self.testrundir, 'slapd.conf')
222+
self._slapd_conf = os.path.join(self.testrundir, 'slapd.d')
222223
self._db_directory = os.path.join(self.testrundir, "openldap-data")
223224
self.ldap_uri = "ldap://%s:%d/" % (self.local_host, self._port)
224225
if HAVE_LDAPI:
@@ -262,6 +263,7 @@ def _find_commands(self):
262263
self.PATH_LDAPDELETE = self._find_command('ldapdelete')
263264
self.PATH_LDAPMODIFY = self._find_command('ldapmodify')
264265
self.PATH_LDAPWHOAMI = self._find_command('ldapwhoami')
266+
self.PATH_SLAPADD = self._find_command('slapadd')
265267

266268
self.PATH_SLAPD = os.environ.get('SLAPD', None)
267269
if not self.PATH_SLAPD:
@@ -292,7 +294,6 @@ def setup_rundir(self):
292294
os.mkdir(self.testrundir)
293295
os.mkdir(self._db_directory)
294296
self._create_sub_dirs(self.testrunsubdirs)
295-
self._ln_schema_files(self.openldap_schema_files, self.SCHEMADIR)
296297

297298
def _cleanup_rundir(self):
298299
"""
@@ -337,17 +338,8 @@ def gen_config(self):
337338
for generating specific static configuration files you have to
338339
override this method
339340
"""
340-
include_directives = '\n'.join(
341-
'include "{schema_prefix}/{schema_file}"'.format(
342-
schema_prefix=self._schema_prefix,
343-
schema_file=schema_file,
344-
)
345-
for schema_file in self.openldap_schema_files
346-
)
347341
config_dict = {
348342
'serverid': hex(self.server_id),
349-
'schema_prefix':self._schema_prefix,
350-
'include_directives': include_directives,
351343
'loglevel': self.slapd_loglevel,
352344
'database': self.database,
353345
'directory': self._db_directory,
@@ -371,29 +363,28 @@ def _create_sub_dirs(self, dir_names):
371363
self._log.debug('Create directory %s', dir_name)
372364
os.mkdir(dir_name)
373365

374-
def _ln_schema_files(self, file_names, source_dir):
375-
"""
376-
write symbolic links to original schema files
377-
"""
378-
for fname in file_names:
379-
ln_source = os.path.join(source_dir, fname)
380-
ln_target = os.path.join(self._schema_prefix, fname)
381-
self._log.debug('Create symlink %s -> %s', ln_source, ln_target)
382-
os.symlink(ln_source, ln_target)
383-
384366
def _write_config(self):
385-
"""Writes the slapd.conf file out, and returns the path to it."""
386-
self._log.debug('Writing config to %s', self._slapd_conf)
387-
with open(self._slapd_conf, 'w') as config_file:
388-
config_file.write(self.gen_config())
389-
self._log.info('Wrote config to %s', self._slapd_conf)
367+
"""Loads the slapd.d configuration."""
368+
self._log.debug("importing configuration: %s", self._slapd_conf)
369+
370+
self.slapadd(self.gen_config(), ["-n0"])
371+
ldif_paths = [
372+
schema
373+
if os.path.exists(schema)
374+
else os.path.join(self.SCHEMADIR, schema)
375+
for schema in self.openldap_schema_files
376+
]
377+
for ldif_path in ldif_paths:
378+
self.slapadd(None, ["-n0", "-l", ldif_path])
379+
380+
self._log.debug("import ok: %s", self._slapd_conf)
390381

391382
def _test_config(self):
392383
self._log.debug('testing config %s', self._slapd_conf)
393384
popen_list = [
394385
self.PATH_SLAPD,
395386
"-Ttest",
396-
"-f", self._slapd_conf,
387+
"-F", self._slapd_conf,
397388
"-u",
398389
"-v",
399390
"-d", "config"
@@ -417,8 +408,7 @@ def _start_slapd(self):
417408
urls.append(self.ldapi_uri)
418409
slapd_args = [
419410
self.PATH_SLAPD,
420-
'-f', self._slapd_conf,
421-
'-F', self.testrundir,
411+
'-F', self._slapd_conf,
422412
'-h', ' '.join(urls),
423413
]
424414
if self._log.isEnabledFor(logging.DEBUG):
@@ -523,10 +513,14 @@ def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None,
523513
stdin_data=None): # pragma: no cover
524514
if ldap_uri is None:
525515
ldap_uri = self.default_ldap_uri
526-
args = [
527-
ldapcommand,
528-
'-H', ldap_uri,
529-
] + self._cli_auth_args() + (extra_args or [])
516+
517+
if ldapcommand.split("/")[-1].startswith("ldap"):
518+
args = [ldapcommand, '-H', ldap_uri] + self._cli_auth_args()
519+
else:
520+
args = [ldapcommand, '-F', self._slapd_conf]
521+
522+
args += (extra_args or [])
523+
530524
self._log.debug('Run command: %r', ' '.join(args))
531525
proc = subprocess.Popen(
532526
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -577,6 +571,16 @@ def ldapdelete(self, dn, recursive=False, extra_args=None):
577571
extra_args.append(dn)
578572
self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args)
579573

574+
def slapadd(self, ldif, extra_args=None):
575+
"""
576+
Runs slapadd on this slapd instance, passing it the ldif content
577+
"""
578+
self._cli_popen(
579+
self.PATH_SLAPADD,
580+
stdin_data=ldif.encode("utf-8") if ldif else None,
581+
extra_args=extra_args,
582+
)
583+
580584
def __enter__(self):
581585
self.start()
582586
return self

Tests/t_ldap_syncrepl.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,44 @@
1919
from slapdtest import SlapdObject, SlapdTestCase
2020

2121
# a template string for generating simple slapd.conf file
22-
SLAPD_CONF_PROVIDER_TEMPLATE = r"""
23-
serverID %(serverid)s
24-
moduleload back_%(database)s
25-
moduleload syncprov
26-
include "%(schema_prefix)s/core.schema"
27-
loglevel %(loglevel)s
28-
allow bind_v2
29-
30-
authz-regexp
31-
"gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth"
32-
"%(rootdn)s"
33-
34-
database %(database)s
35-
directory "%(directory)s"
36-
suffix "%(suffix)s"
37-
rootdn "%(rootdn)s"
38-
rootpw "%(rootpw)s"
39-
overlay syncprov
40-
syncprov-checkpoint 100 10
41-
syncprov-sessionlog 100
42-
index objectclass,entryCSN,entryUUID eq
22+
SLAPD_CONF_PROVIDER_TEMPLATE = r"""dn: cn=config
23+
objectClass: olcGlobal
24+
cn: config
25+
olcServerID: %(serverid)s
26+
olcLogLevel: %(loglevel)s
27+
olcAllows: bind_v2
28+
olcAuthzRegexp: {0}"gidnumber=%(root_gid)s\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" "%(rootdn)s"
29+
olcAuthzRegexp: {1}"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" "ldap://ou=people,dc=local???($1)"
30+
olcTLSCACertificateFile: %(cafile)s
31+
olcTLSCertificateFile: %(servercert)s
32+
olcTLSCertificateKeyFile: %(serverkey)s
33+
olcTLSVerifyClient: try
34+
35+
dn: cn=module,cn=config
36+
objectClass: olcModuleList
37+
cn: module
38+
olcModuleLoad: back_%(database)s
39+
olcModuleLoad: syncprov
40+
41+
dn: olcDatabase=%(database)s,cn=config
42+
objectClass: olcDatabaseConfig
43+
objectClass: olcMdbConfig
44+
olcDatabase: %(database)s
45+
olcSuffix: %(suffix)s
46+
olcRootDN: %(rootdn)s
47+
olcRootPW: %(rootpw)s
48+
olcDbDirectory: %(directory)s
49+
olcDbIndex: objectclass,entryCSN,entryUUID eq
50+
51+
dn: olcOverlay=syncprov,olcDatabase={1}%(database)s,cn=config
52+
objectClass: olcOverlayConfig
53+
objectClass: olcSyncProvConfig
54+
olcOverlay: syncprov
55+
olcSpCheckpoint: 100 10
56+
olcSpSessionlog: 100
57+
"""
58+
59+
OTHER_CONF = r"""
4360
"""
4461

4562
# Define initial data load, both as an LDIF and as a dictionary.

Tests/t_ldapobject.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@
6262
6363
"""
6464

65+
SCHEMA_TEMPLATE = """dn: cn=mySchema,cn=schema,cn=config
66+
objectClass: olcSchemaConfig
67+
cn: mySchema
68+
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.1 NAME 'myAttribute'
69+
DESC 'fobar attribute'
70+
EQUALITY caseExactMatch
71+
ORDERING caseExactOrderingMatch
72+
SUBSTR caseExactSubstringsMatch
73+
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
74+
SINGLE-VALUE
75+
USAGE userApplications
76+
X-ORIGIN 'foobar' )
77+
olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.2 NAME 'myClass'
78+
DESC 'foobar objectclass'
79+
SUP top
80+
STRUCTURAL
81+
MUST myAttribute
82+
X-ORIGIN 'foobar' )"""
83+
6584

6685
class Test00_SimpleLDAPObject(SlapdTestCase):
6786
"""
@@ -94,6 +113,14 @@ def setUp(self):
94113
def tearDown(self):
95114
del self._ldap_conn
96115

116+
def reset_connection(self):
117+
try:
118+
del self._ldap_conn
119+
except AttributeError:
120+
pass
121+
122+
self._ldap_conn = self._open_ldap_conn(bytes_mode=False)
123+
97124
def test_reject_bytes_base(self):
98125
base = self.server.suffix
99126
l = self._ldap_conn
@@ -465,6 +492,22 @@ def test_passwd_s(self):
465492

466493
l.delete_s(dn)
467494

495+
def test_slapadd(self):
496+
with self.assertRaises(ldap.INVALID_DN_SYNTAX):
497+
self._ldap_conn.add_s("myAttribute=foobar,ou=Container,%s" % self.server.suffix, [
498+
("objectClass", b'myClass'),
499+
("myAttribute", b'foobar'),
500+
])
501+
502+
self.server.slapadd(SCHEMA_TEMPLATE, ["-n0"])
503+
self.server.restart()
504+
self.reset_connection()
505+
506+
self._ldap_conn.add_s("myAttribute=foobar,ou=Container,%s" % self.server.suffix, [
507+
("objectClass", b'myClass'),
508+
("myAttribute", b'foobar'),
509+
])
510+
468511

469512
class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
470513
"""
@@ -561,6 +604,11 @@ def tearDown(self):
561604
del self._sock
562605
super(Test03_SimpleLDAPObjectWithFileno, self).tearDown()
563606

607+
def reset_connection(self):
608+
self._sock.close()
609+
del self._sock
610+
super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection()
611+
564612

565613
if __name__ == '__main__':
566614
unittest.main()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy