diff --git a/.travis.yml b/.travis.yml index 5f1ef520..711ac579 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,6 @@ addons: - slapd matrix: - allow_failures: - - env: TOXENV=py37-pytest - - env: TOXENV=py37-lint include: - python: 3.7 env: TOXENV=py37-pytest @@ -17,7 +14,7 @@ matrix: env: TOXENV=py37-lint - python: 3.7 env: TOXENV=format-check - - python: 3.5 + - python: 3.7 env: TOXENV=docs install: diff --git a/doc/ldap.rst b/doc/ldap.rst index a9c91dc5..31e091ea 100644 --- a/doc/ldap.rst +++ b/doc/ldap.rst @@ -434,7 +434,7 @@ Here is how it's used (I don't understand why a path is not provided): And here is its docstring: -.. automethod:: moulinette.authenticators.ldap.Authenticator.update +.. automethod:: moulinette.authenticators.ldap.Authenticator.validate_uniqueness Get conflict ============ diff --git a/doc/utils/stream.rst b/doc/utils/stream.rst index 5d9dd34f..b3205f02 100644 --- a/doc/utils/stream.rst +++ b/doc/utils/stream.rst @@ -1,4 +1,4 @@ Stream operation utils ====================== -.. autofunction:: moulinette.utils.stream.async_file_reading +.. autofunction:: moulinette.utils.stream.LogPipe diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index c36b5da8..0170d345 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -130,7 +130,7 @@ class BaseAuthenticator(object): s_id, s_token = token # Attempt to authenticate self._authenticate_session(s_id, s_token) - except MoulinetteError as e: + except MoulinetteError: raise except Exception as e: logger.exception( diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 4c2c7e96..5d3d170c 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -558,7 +558,11 @@ class HTTPBadRequestResponse(HTTPResponse): if isinstance(error, MoulinetteError): content = error.content() if isinstance(content, dict): - super(HTTPBadRequestResponse, self).__init__(json_encode(content), 400, headers={'Content-type': 'application/json'}) + super(HTTPBadRequestResponse, self).__init__( + json_encode(content), + 400, + headers={"Content-type": "application/json"}, + ) else: super(HTTPBadRequestResponse, self).__init__(content, 400) else: diff --git a/moulinette/utils/process.py b/moulinette/utils/process.py index e4134498..dac970e0 100644 --- a/moulinette/utils/process.py +++ b/moulinette/utils/process.py @@ -72,11 +72,18 @@ def call_async_output(args, callback, **kwargs): kwargs["env"] = os.environ kwargs["env"]["YNH_STDINFO"] = str(stdinfo.fdWrite) - with subprocess.Popen(args, **kwargs) as p: + try: + with subprocess.Popen(args, **kwargs) as p: + kwargs["stdout"].close() + kwargs["stderr"].close() + if stdinfo: + stdinfo.close() + except TypeError: kwargs["stdout"].close() kwargs["stderr"].close() if stdinfo: stdinfo.close() + raise # on slow hardware, in very edgy situations it is possible that the process # isn't finished just after having closed stdout and stderr, so we wait a diff --git a/test/src/ldap_server.py b/test/src/ldap_server.py index 67fbcc46..81cc0cb5 100644 --- a/test/src/ldap_server.py +++ b/test/src/ldap_server.py @@ -1,7 +1,4 @@ -try: - import slapdtest -except ImportError: - import old_slapdtest as slapdtest +import slapdtest import os from moulinette.authenticators import ldap as m_ldap @@ -60,7 +57,7 @@ class LDAPServer: import yaml with open(os.path.join(HERE, "..", "ldap_files", "ldap_scheme.yml"), "rb") as f: - ldap_map = yaml.load(f) + ldap_map = yaml.safe_load(f) def _get_ldap_interface(): conf = { diff --git a/test/src/old_slapdtest/README b/test/src/old_slapdtest/README deleted file mode 100644 index a6e34008..00000000 --- a/test/src/old_slapdtest/README +++ /dev/null @@ -1,2 +0,0 @@ -I have adapted the code from https://github.com/python-ldap/python-ldap/tree/master/Lib/slapdtest since the version of python-ldap we use does not provide the import slapdtest. -This part will must be removed once we switch to python3 \ No newline at end of file diff --git a/test/src/old_slapdtest/__init__.py b/test/src/old_slapdtest/__init__.py deleted file mode 100644 index a80062b1..00000000 --- a/test/src/old_slapdtest/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# flake8: noqa -from ._slapdtest import SlapdObject diff --git a/test/src/old_slapdtest/_slapdtest.py b/test/src/old_slapdtest/_slapdtest.py deleted file mode 100644 index d65b79f7..00000000 --- a/test/src/old_slapdtest/_slapdtest.py +++ /dev/null @@ -1,659 +0,0 @@ -# -*- coding: utf-8 -*- -# flake8: noqa -""" -slapdtest - module for spawning test instances of OpenLDAP's slapd server -See https://www.python-ldap.org/ for details. -""" - -from __future__ import unicode_literals - -import os -import socket -import sys -import time -import subprocess -import logging -import atexit -from logging.handlers import SysLogHandler -import unittest - -# Switch off processing .ldaprc or ldap.conf before importing _ldap -os.environ["LDAPNOINIT"] = "1" - -import ldap -from urllib import quote_plus - -try: - from shutil import which -except ImportError: - # shutil.which() from Python 3.6 - # "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, - # 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; - # All Rights Reserved" - def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - """ - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if not os.curdir in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if not normdir in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -HERE = os.path.abspath(os.path.dirname(__file__)) - -# a template string for generating simple slapd.conf file -SLAPD_CONF_TEMPLATE = r""" -serverID %(serverid)s -moduleload back_%(database)s -%(include_directives)s -loglevel %(loglevel)s -allow bind_v2 -authz-regexp - "gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" - "%(rootdn)s" -database %(database)s -directory "%(directory)s" -suffix "%(suffix)s" -rootdn "%(rootdn)s" -rootpw "%(rootpw)s" -TLSCACertificateFile "%(cafile)s" -TLSCertificateFile "%(servercert)s" -TLSCertificateKeyFile "%(serverkey)s" -# ignore missing client cert but fail with invalid client cert -TLSVerifyClient try -authz-regexp - "C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" - "ldap://ou=people,dc=local???($1)" -""" - -LOCALHOST = "127.0.0.1" - -CI_DISABLED = set(os.environ.get("CI_DISABLED", "").split(":")) -if "LDAPI" in CI_DISABLED: - HAVE_LDAPI = False -else: - HAVE_LDAPI = hasattr(socket, "AF_UNIX") - - -def identity(test_item): - """Identity decorator""" - return test_item - - -def skip_unless_ci(reason, feature=None): - """Skip test unless test case is executed on CI like Travis CI""" - if not os.environ.get("CI", False): - return unittest.skip(reason) - elif feature in CI_DISABLED: - return unittest.skip(reason) - else: - # Don't skip on Travis - return identity - - -def requires_tls(): - """Decorator for TLS tests - Tests are not skipped on CI (e.g. Travis CI) - """ - if not ldap.TLS_AVAIL: - return skip_unless_ci("test needs ldap.TLS_AVAIL", feature="TLS") - else: - return identity - - -def requires_sasl(): - if not ldap.SASL_AVAIL: - return skip_unless_ci("test needs ldap.SASL_AVAIL", feature="SASL") - else: - return identity - - -def requires_ldapi(): - if not HAVE_LDAPI: - return skip_unless_ci("test needs ldapi support (AF_UNIX)", feature="LDAPI") - else: - return identity - - -def _add_sbin(path): - """Add /sbin and related directories to a command search path""" - directories = path.split(os.pathsep) - if sys.platform != "win32": - for sbin in "/usr/local/sbin", "/sbin", "/usr/sbin": - if sbin not in directories: - directories.append(sbin) - return os.pathsep.join(directories) - - -def combined_logger( - log_name, - log_level=logging.WARN, - sys_log_format="%(levelname)s %(message)s", - console_log_format="%(asctime)s %(levelname)s %(message)s", -): - """ - Returns a combined SysLogHandler/StreamHandler logging instance - with formatters - """ - if "LOGLEVEL" in os.environ: - log_level = os.environ["LOGLEVEL"] - try: - log_level = int(log_level) - except ValueError: - pass - # for writing to syslog - new_logger = logging.getLogger(log_name) - if sys_log_format and os.path.exists("/dev/log"): - my_syslog_formatter = logging.Formatter( - fmt=" ".join((log_name, sys_log_format)) - ) - my_syslog_handler = logging.handlers.SysLogHandler( - address="/dev/log", - facility=SysLogHandler.LOG_DAEMON, - ) - my_syslog_handler.setFormatter(my_syslog_formatter) - new_logger.addHandler(my_syslog_handler) - if console_log_format: - my_stream_formatter = logging.Formatter(fmt=console_log_format) - my_stream_handler = logging.StreamHandler() - my_stream_handler.setFormatter(my_stream_formatter) - new_logger.addHandler(my_stream_handler) - new_logger.setLevel(log_level) - return new_logger # end of combined_logger() - - -class SlapdObject(object): - """ - Controller class for a slapd instance, OpenLDAP's server. - This class creates a temporary data store for slapd, runs it - listening on a private Unix domain socket and TCP port, - and initializes it with a top-level entry and the root user. - When a reference to an instance of this class is lost, the slapd - server is shut down. - An instance can be used as a context manager. When exiting the context - manager, the slapd server is shut down and the temporary data store is - removed. - .. versionchanged:: 3.1 - Added context manager functionality - """ - - slapd_conf_template = SLAPD_CONF_TEMPLATE - database = "mdb" - suffix = "dc=slapd-test,dc=python-ldap,dc=org" - root_cn = "Manager" - root_pw = "password" - slapd_loglevel = "stats stats2" - local_host = "127.0.0.1" - testrunsubdirs = ("schema",) - openldap_schema_files = ("core.schema",) - - TMPDIR = os.environ.get("TMP", os.getcwd()) - if "SCHEMA" in os.environ: - SCHEMADIR = os.environ["SCHEMA"] - elif os.path.isdir("/etc/openldap/schema"): - SCHEMADIR = "/etc/openldap/schema" - elif os.path.isdir("/etc/ldap/schema"): - SCHEMADIR = "/etc/ldap/schema" - else: - SCHEMADIR = None - - BIN_PATH = os.environ.get("BIN", os.environ.get("PATH", os.defpath)) - SBIN_PATH = os.environ.get("SBIN", _add_sbin(BIN_PATH)) - - # time in secs to wait before trying to access slapd via LDAP (again) - _start_sleep = 1.5 - - # create loggers once, multiple calls mess up refleak tests - _log = combined_logger("python-ldap-test") - - def __init__(self): - self._proc = None - self._port = self._avail_tcp_port() - self.server_id = self._port % 4096 - self.testrundir = os.path.join(self.TMPDIR, "python-ldap-test-%d" % self._port) - self._schema_prefix = os.path.join(self.testrundir, "schema") - self._slapd_conf = os.path.join(self.testrundir, "slapd.conf") - self._db_directory = os.path.join(self.testrundir, "openldap-data") - self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) - if HAVE_LDAPI: - ldapi_path = os.path.join(self.testrundir, "ldapi") - self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) - self.default_ldap_uri = self.ldapi_uri - # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools - self.cli_sasl_external = ldap.SASL_AVAIL - else: - self.ldapi_uri = None - self.default_ldap_uri = self.ldap_uri - # Use simple bind via LDAP uri - self.cli_sasl_external = False - - self._find_commands() - - if self.SCHEMADIR is None: - raise ValueError("SCHEMADIR is None, ldap schemas are missing.") - - # TLS certs - self.cafile = os.path.join(HERE, "certs/ca.pem") - self.servercert = os.path.join(HERE, "certs/server.pem") - self.serverkey = os.path.join(HERE, "certs/server.key") - self.clientcert = os.path.join(HERE, "certs/client.pem") - self.clientkey = os.path.join(HERE, "certs/client.key") - - @property - def root_dn(self): - return "cn={self.root_cn},{self.suffix}".format(self=self) - - def _find_commands(self): - self.PATH_LDAPADD = self._find_command("ldapadd") - self.PATH_LDAPDELETE = self._find_command("ldapdelete") - self.PATH_LDAPMODIFY = self._find_command("ldapmodify") - self.PATH_LDAPWHOAMI = self._find_command("ldapwhoami") - - self.PATH_SLAPD = os.environ.get("SLAPD", None) - if not self.PATH_SLAPD: - self.PATH_SLAPD = self._find_command("slapd", in_sbin=True) - - def _find_command(self, cmd, in_sbin=False): - if in_sbin: - path = self.SBIN_PATH - var_name = "SBIN" - else: - path = self.BIN_PATH - var_name = "BIN" - command = which(cmd, path=path) - if command is None: - raise ValueError( - "Command '{}' not found. Set the {} environment variable to " - "override slapdtest's search path.".format(cmd, var_name) - ) - return command - - def setup_rundir(self): - """ - creates rundir structure - for setting up a custom directory structure you have to override - this method - """ - os.mkdir(self.testrundir) - os.mkdir(self._db_directory) - self._create_sub_dirs(self.testrunsubdirs) - self._ln_schema_files(self.openldap_schema_files, self.SCHEMADIR) - - def _cleanup_rundir(self): - """ - Recursively delete whole directory specified by `path' - """ - # cleanup_rundir() is called in atexit handler. Until Python 3.4, - # the rest of the world is already destroyed. - import os, os.path - - if not os.path.exists(self.testrundir): - return - self._log.debug("clean-up %s", self.testrundir) - for dirpath, dirnames, filenames in os.walk(self.testrundir, topdown=False): - for filename in filenames: - self._log.debug("remove %s", os.path.join(dirpath, filename)) - os.remove(os.path.join(dirpath, filename)) - for dirname in dirnames: - self._log.debug("rmdir %s", os.path.join(dirpath, dirname)) - os.rmdir(os.path.join(dirpath, dirname)) - os.rmdir(self.testrundir) - self._log.info("cleaned-up %s", self.testrundir) - - def _avail_tcp_port(self): - """ - find an available port for TCP connection - """ - sock = socket.socket() - try: - sock.bind((self.local_host, 0)) - port = sock.getsockname()[1] - finally: - sock.close() - self._log.info("Found available port %d", port) - return port - - def gen_config(self): - """ - generates a slapd.conf and returns it as one string - for generating specific static configuration files you have to - override this method - """ - include_directives = "\n".join( - 'include "{schema_prefix}/{schema_file}"'.format( - schema_prefix=self._schema_prefix, - schema_file=schema_file, - ) - for schema_file in self.openldap_schema_files - ) - config_dict = { - "serverid": hex(self.server_id), - "schema_prefix": self._schema_prefix, - "include_directives": include_directives, - "loglevel": self.slapd_loglevel, - "database": self.database, - "directory": self._db_directory, - "suffix": self.suffix, - "rootdn": self.root_dn, - "rootpw": self.root_pw, - "root_uid": os.getuid(), - "root_gid": os.getgid(), - "cafile": self.cafile, - "servercert": self.servercert, - "serverkey": self.serverkey, - } - return self.slapd_conf_template % config_dict - - def _create_sub_dirs(self, dir_names): - """ - create sub-directories beneath self.testrundir - """ - for dname in dir_names: - dir_name = os.path.join(self.testrundir, dname) - self._log.debug("Create directory %s", dir_name) - os.mkdir(dir_name) - - def _ln_schema_files(self, file_names, source_dir): - """ - write symbolic links to original schema files - """ - for fname in file_names: - ln_source = os.path.join(source_dir, fname) - ln_target = os.path.join(self._schema_prefix, fname) - self._log.debug("Create symlink %s -> %s", ln_source, ln_target) - os.symlink(ln_source, ln_target) - - def _write_config(self): - """Writes the slapd.conf file out, and returns the path to it.""" - self._log.debug("Writing config to %s", self._slapd_conf) - with open(self._slapd_conf, "w") as config_file: - config_file.write(self.gen_config()) - self._log.info("Wrote config to %s", self._slapd_conf) - - def _test_config(self): - self._log.debug("testing config %s", self._slapd_conf) - popen_list = [ - self.PATH_SLAPD, - "-Ttest", - "-f", - self._slapd_conf, - "-u", - ] - if self._log.isEnabledFor(logging.DEBUG): - popen_list.append("-v") - popen_list.extend(["-d", "config"]) - else: - popen_list.append("-Q") - proc = subprocess.Popen(popen_list) - if proc.wait() != 0: - raise RuntimeError("configuration test failed") - self._log.info("config ok: %s", self._slapd_conf) - - def _start_slapd(self): - """ - Spawns/forks the slapd process - """ - urls = [self.ldap_uri] - if self.ldapi_uri: - urls.append(self.ldapi_uri) - slapd_args = [ - self.PATH_SLAPD, - "-f", - self._slapd_conf, - "-F", - self.testrundir, - "-h", - " ".join(urls), - ] - if self._log.isEnabledFor(logging.DEBUG): - slapd_args.extend(["-d", "-1"]) - else: - slapd_args.extend(["-d", "0"]) - self._log.info("starting slapd: %r", " ".join(slapd_args)) - self._proc = subprocess.Popen(slapd_args) - # Waits until the LDAP server socket is open, or slapd crashed - # no cover to avoid spurious coverage changes, see - # https://github.com/python-ldap/python-ldap/issues/127 - for _ in range(10): # pragma: no cover - if self._proc.poll() is not None: - self._stopped() - raise RuntimeError("slapd exited before opening port") - time.sleep(self._start_sleep) - try: - self._log.debug("slapd connection check to %s", self.default_ldap_uri) - self.ldapwhoami() - except RuntimeError: - pass - else: - return - raise RuntimeError("slapd did not start properly") - - def start(self): - """ - Starts the slapd server process running, and waits for it to come up. - """ - - if self._proc is None: - # prepare directory structure - atexit.register(self.stop) - self._cleanup_rundir() - self.setup_rundir() - self._write_config() - self._test_config() - self._start_slapd() - self._log.debug( - "slapd with pid=%d listening on %s and %s", - self._proc.pid, - self.ldap_uri, - self.ldapi_uri, - ) - - def stop(self): - """ - Stops the slapd server, and waits for it to terminate and cleans up - """ - if self._proc is not None: - self._log.debug("stopping slapd with pid %d", self._proc.pid) - self._proc.terminate() - self.wait() - self._cleanup_rundir() - if hasattr(atexit, "unregister"): - # Python 3 - atexit.unregister(self.stop) - elif hasattr(atexit, "_exithandlers"): - # Python 2, can be None during process shutdown - try: - atexit._exithandlers.remove(self.stop) - except ValueError: - pass - - def restart(self): - """ - Restarts the slapd server with same data - """ - self._proc.terminate() - self.wait() - self._start_slapd() - - def wait(self): - """Waits for the slapd process to terminate by itself.""" - if self._proc: - self._proc.wait() - self._stopped() - - def _stopped(self): - """Called when the slapd server is known to have terminated""" - if self._proc is not None: - self._log.info("slapd[%d] terminated", self._proc.pid) - self._proc = None - - def _cli_auth_args(self): - if self.cli_sasl_external: - authc_args = [ - "-Y", - "EXTERNAL", - ] - if not self._log.isEnabledFor(logging.DEBUG): - authc_args.append("-Q") - else: - authc_args = [ - "-x", - "-D", - self.root_dn, - "-w", - self.root_pw, - ] - return authc_args - - # no cover to avoid spurious coverage changes - def _cli_popen( - self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None - ): # pragma: no cover - if ldap_uri is None: - ldap_uri = self.default_ldap_uri - args = ( - [ - ldapcommand, - "-H", - ldap_uri, - ] - + self._cli_auth_args() - + (extra_args or []) - ) - self._log.debug("Run command: %r", " ".join(args)) - proc = subprocess.Popen( - args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - self._log.debug("stdin_data=%r", stdin_data) - stdout_data, stderr_data = proc.communicate(stdin_data) - if stdout_data is not None: - self._log.debug("stdout_data=%r", stdout_data) - if stderr_data is not None: - self._log.debug("stderr_data=%r", stderr_data) - if proc.wait() != 0: - raise RuntimeError( - "{!r} process failed:\n{!r}\n{!r}".format( - args, stdout_data, stderr_data - ) - ) - return stdout_data, stderr_data - - def ldapwhoami(self, extra_args=None): - """ - Runs ldapwhoami on this slapd instance - """ - self._cli_popen(self.PATH_LDAPWHOAMI, extra_args=extra_args) - - def ldapadd(self, ldif, extra_args=None): - """ - Runs ldapadd on this slapd instance, passing it the ldif content - """ - self._cli_popen( - self.PATH_LDAPADD, extra_args=extra_args, stdin_data=ldif.encode("utf-8") - ) - - def ldapmodify(self, ldif, extra_args=None): - """ - Runs ldapadd on this slapd instance, passing it the ldif content - """ - self._cli_popen( - self.PATH_LDAPMODIFY, extra_args=extra_args, stdin_data=ldif.encode("utf-8") - ) - - def ldapdelete(self, dn, recursive=False, extra_args=None): - """ - Runs ldapdelete on this slapd instance, deleting 'dn' - """ - if extra_args is None: - extra_args = [] - if recursive: - extra_args.append("-r") - extra_args.append(dn) - self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) - - def __enter__(self): - self.start() - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.stop() - - -class SlapdTestCase(unittest.TestCase): - """ - test class which also clones or initializes a running slapd - """ - - server_class = SlapdObject - server = None - ldap_object_class = None - - def _open_ldap_conn(self, who=None, cred=None, **kwargs): - """ - return a LDAPObject instance after simple bind - """ - ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs) - ldap_conn.protocol_version = 3 - # ldap_conn.set_option(ldap.OPT_REFERRALS, 0) - ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw) - return ldap_conn - - @classmethod - def setUpClass(cls): - cls.server = cls.server_class() - cls.server.start() - - @classmethod - def tearDownClass(cls): - cls.server.stop() diff --git a/test/src/old_slapdtest/certs/README b/test/src/old_slapdtest/certs/README deleted file mode 100644 index 4be616ae..00000000 --- a/test/src/old_slapdtest/certs/README +++ /dev/null @@ -1,24 +0,0 @@ -python-ldap test certificates -============================= - -Certificates and keys ---------------------- - -* ``ca.pem``: internal root CA certificate -* ``server.pem``: TLS server certificate for slapd, signed by root CA. The - server cert is valid for DNS Name ``localhost`` and IPs ``127.0.0.1`` and - ``:1``. -* ``server.key``: private key for ``server.pem``, no password protection -* ``client.pem``: certificate for TLS client cert authentication, signed by - root CA. -* ``client.key``: private key for ``client.pem``, no password protection - -Configuration and scripts -------------------------- - -* ``ca.conf`` contains the CA definition as well as extensions for the - client and server certificates. -* ``client.conf`` and ``server.conf`` hold the subject and base configuration - for server and client certs. -* ``gencerts.sh`` creates new CA, client and server certificates. -* ``gennssdb.sh`` can be used to create a NSSDB for all certs and keys. diff --git a/test/src/old_slapdtest/certs/ca.conf b/test/src/old_slapdtest/certs/ca.conf deleted file mode 100644 index d1d89e18..00000000 --- a/test/src/old_slapdtest/certs/ca.conf +++ /dev/null @@ -1,77 +0,0 @@ -# Written by Christian Heimes - -[default] -ca = "ca" -tmpdir = $ENV::CATMPDIR -outdir = $ENV::CAOUTDIR -name_opt = multiline,-esc_msb,utf8 - -[req] -default_bits = 2048 -encrypt_key = no -default_md = sha256 -utf8 = yes -string_mask = utf8only -prompt = no -distinguished_name = ca_dn - -[ca_dn] -countryName = "DE" -organizationName = "python-ldap" -organizationalUnitName = "slapd-test" -commonName = "Python LDAP Test CA" - -[ca] -default_ca = python_ldap_ca - -[python_ldap_ca] -certificate = $outdir/$ca.pem -private_key = $outdir/$ca.key -new_certs_dir = $tmpdir -serial = $tmpdir/$ca.crt.srl -crlnumber = $tmpdir/$ca.crl.srl -database = $tmpdir/$ca.db -unique_subject = no -default_days = 365200 -default_md = sha256 -policy = match_pol -email_in_dn = no -preserve = no -name_opt = $name_opt -cert_opt = ca_default -copy_extensions = none -default_crl_days = 365100 - -[match_pol] -countryName = match -stateOrProvinceName = optional -localityName = optional -organizationName = match -organizationalUnitName = match -commonName = supplied - -[ca_ext] -basicConstraints = critical,CA:true -keyUsage = critical,keyCertSign,cRLSign -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always - -[server_san] -DNS.1 = localhost -IP.1 = 127.0.0.1 -IP.2 = ::1 - -[server_ext] -basicConstraints = critical,CA:false -keyUsage = critical,digitalSignature,keyEncipherment -extendedKeyUsage = critical,serverAuth -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always -subjectAltName = @server_san - -[client_ext] -basicConstraints = critical,CA:false -keyUsage = critical,digitalSignature -extendedKeyUsage = critical,clientAuth -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always diff --git a/test/src/old_slapdtest/certs/ca.pem b/test/src/old_slapdtest/certs/ca.pem deleted file mode 100644 index b52ffafb..00000000 --- a/test/src/old_slapdtest/certs/ca.pem +++ /dev/null @@ -1,80 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA - Validity - Not Before: Apr 12 18:52:38 2019 GMT - Not After : Oct 17 18:52:38 2994 GMT - Subject: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:d7:30:73:20:44:7d:83:d4:c7:01:b8:ab:1e:7c: - 91:f4:38:ac:9c:41:43:64:0c:31:99:48:70:22:7d: - ae:1b:47:e7:2a:28:4d:f7:46:4e:b4:ba:ae:c0:9d: - d5:1f:4b:7a:79:2f:b9:dc:68:7f:79:84:88:50:51: - 3b:7d:dc:d5:57:17:66:45:c0:2c:20:13:f7:99:d6: - 9d:e2:12:7c:41:76:82:51:19:2c:b6:ff:46:cb:04: - 56:38:22:2a:c3:7a:b5:71:51:49:4e:62:68:a0:99: - 6f:de:f3:a2:0f:a2:aa:1b:72:a5:87:bc:42:5a:a7: - 22:8d:33:b4:88:a8:dc:5d:72:ca:dd:a0:9a:4e:db: - 7d:8b:10:de:c5:41:e9:e9:8d:fa:6c:dd:94:6e:b1: - 31:c2:6d:a1:69:6c:7a:3a:b2:76:65:c9:e5:95:38: - 62:40:81:c6:29:26:26:d1:d1:c1:f4:5e:fa:24:ef: - 13:da:24:13:6f:f5:5c:ba:b1:31:8f:30:94:71:7b: - c6:e5:da:b9:b5:64:39:39:09:c2:4a:80:64:58:1d: - 99:f5:65:3c:a7:26:08:95:26:35:7b:fa:e7:20:08: - ff:72:df:9b:8f:9f:da:8b:c3:a7:8b:fc:8c:c0:a5: - 31:87:1d:4c:14:f6:cf:90:5e:2e:6e:a6:db:27:08: - eb:df - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Subject Key Identifier: - BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 - X509v3 Authority Key Identifier: - keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 - - Signature Algorithm: sha256WithRSAEncryption - 06:20:1f:eb:42:6a:42:62:b1:ee:69:c8:cd:47:a6:2e:69:95: - 59:dc:49:09:69:40:93:25:a1:ec:6d:3a:dd:dc:e5:74:ab:33: - 9d:8f:cc:e3:bb:7a:3f:5b:51:58:74:f7:bd:6c:7c:3c:b6:5a: - 05:50:a8:8c:c3:fb:5b:75:2a:c2:6c:06:93:4c:a9:93:71:1c: - 51:e5:be:a1:24:93:e2:79:ca:ea:08:86:90:b9:70:e7:7a:40: - bf:f4:d6:71:f4:4d:c0:0f:e0:31:a0:23:46:77:30:72:a9:62: - 8a:2a:12:c4:dd:3d:86:ae:f7:6b:33:80:26:58:49:53:ff:cd: - 8a:c6:f6:11:2c:b3:ff:a5:8e:1c:f8:22:e2:1b:8e:04:33:fb: - 0d:da:31:86:12:9f:d1:03:86:9c:6a:78:5e:3c:5e:8a:52:aa: - 68:1f:ff:f9:17:75:b0:da:f2:99:3c:80:3c:96:2a:33:07:54: - 59:84:e7:92:34:0f:99:76:e3:d6:4d:4d:9c:fb:21:35:f9:cb: - a5:30:80:8b:9d:61:90:d3:d4:59:3a:2f:f2:f6:20:13:7e:26: - dc:50:b0:49:3e:19:fe:eb:7d:cf:b9:1a:5d:5c:3a:76:30:d9: - 0e:d7:df:de:ce:a9:c4:21:df:63:b9:d0:64:86:0b:28:9a:2e: - ab:51:73:e4 ------BEGIN CERTIFICATE----- -MIIDjDCCAnSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU -MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMjk5NDEw -MTcxODUyMzhaMFYxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET -MBEGA1UECwwKc2xhcGQtdGVzdDEcMBoGA1UEAwwTUHl0aG9uIExEQVAgVGVzdCBD -QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANcwcyBEfYPUxwG4qx58 -kfQ4rJxBQ2QMMZlIcCJ9rhtH5yooTfdGTrS6rsCd1R9Lenkvudxof3mEiFBRO33c -1VcXZkXALCAT95nWneISfEF2glEZLLb/RssEVjgiKsN6tXFRSU5iaKCZb97zog+i -qhtypYe8QlqnIo0ztIio3F1yyt2gmk7bfYsQ3sVB6emN+mzdlG6xMcJtoWlsejqy -dmXJ5ZU4YkCBxikmJtHRwfRe+iTvE9okE2/1XLqxMY8wlHF7xuXaubVkOTkJwkqA -ZFgdmfVlPKcmCJUmNXv65yAI/3Lfm4+f2ovDp4v8jMClMYcdTBT2z5BeLm6m2ycI -698CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFL141UrxkJbF6OxmSSNHA18mc4ayMB8GA1UdIwQYMBaAFL141UrxkJbF -6OxmSSNHA18mc4ayMA0GCSqGSIb3DQEBCwUAA4IBAQAGIB/rQmpCYrHuacjNR6Yu -aZVZ3EkJaUCTJaHsbTrd3OV0qzOdj8zju3o/W1FYdPe9bHw8tloFUKiMw/tbdSrC -bAaTTKmTcRxR5b6hJJPiecrqCIaQuXDnekC/9NZx9E3AD+AxoCNGdzByqWKKKhLE -3T2GrvdrM4AmWElT/82KxvYRLLP/pY4c+CLiG44EM/sN2jGGEp/RA4acanhePF6K -UqpoH//5F3Ww2vKZPIA8liozB1RZhOeSNA+ZduPWTU2c+yE1+culMICLnWGQ09RZ -Oi/y9iATfibcULBJPhn+633PuRpdXDp2MNkO19/ezqnEId9judBkhgsomi6rUXPk ------END CERTIFICATE----- diff --git a/test/src/old_slapdtest/certs/client.conf b/test/src/old_slapdtest/certs/client.conf deleted file mode 100644 index 774dc3a4..00000000 --- a/test/src/old_slapdtest/certs/client.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Written by Christian Heimes - -[req] -default_bits = 2048 -encrypt_key = no -default_md = sha256 -utf8 = yes -string_mask = utf8only -prompt = no -distinguished_name = client_dn - -[client_dn] -countryName = "DE" -organizationName = "python-ldap" -organizationalUnitName = "slapd-test" -commonName = "client" diff --git a/test/src/old_slapdtest/certs/client.key b/test/src/old_slapdtest/certs/client.key deleted file mode 100644 index 7213c0b4..00000000 --- a/test/src/old_slapdtest/certs/client.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDjt5O6nRrnAWPm -T0JvRLBHMclll92IWF/O4GEdcJ5fbBxP3BxK0Dv+6aRcR7b2o0f6fk/bgNepXfv/ -MXDQcFlESbfmUNGshFmZr0sjPrYPD1R06TZs+/7RsMXnx1c79mFGEQ4wqzDOBHKQ -xeDhNJk+BcE0QABsqF8AA2XC2/dK14QCljKLC84k1zTFTnh8duN2eAalaPQFFOoj -4AnonUnswJ45zIx5V2BdG+oqO5dwo/cEukKgAEL8T2IJ9Cqlmh2sPbMqYC8cODq6 -YcugMznxrfHV5LNThfkvwMe26+vv68r65zalPDy0M+cUMTMyBVY4TL3fejrloY2t -YMhPJIclAgMBAAECggEAPXdd/u9NRbGQX6hhTFuEIZOEw1F80MLaCaNzU1kExskN -01icom0W5LX4UZhiAK0OTsUtlRhwHh1qWfXkd777uX0UkKycDC8laGByra7Nwb7n -ky8oK77Rh5RptyiNmXflxd3wsJ5k7BczPXTMQL3L53vyLMJh2vKPwhcorrJlS+Pi -JjINMaR4IrDlpMYlrn9NTjsGr+mj/pdmKfU/KVXeKzFcwKTjUnDJNSbGDIC0AxaJ -dGU0yIX9MPW+p5szcA9o22UWW4LsEFY4YABeCqbm9/UQt3jWVMjCy4AOgr/9HWSR -DvXI/Xtdl3CTCr8+qDnhBaUI27z+UelZfTBFKUb8AQKBgQD6SmtrTBgEfb6tuxJw -AAHRuUcWGjatZ7X+meHRC9B7UPxUrKl9tU5NC7Gz6YMt+vr4bNMwykI6Ndj+4tSJ -KqsAC86v19CH4usMBLZ68MeTRvtQGiPah71syYrxf0uvYOx/KzUUBX240Ls+lEbE -W33psMoNAezUPpJwKx7CMjcBgQKBgQDo6VaT59bKRc3DXJvqFjd7TPIex+ny6JK+ -8oOwyyFFBwkzfymoOxN4lxSrE6yf7uTemRRn+RIH3UGDottIDqzhjvtcV5uODeIN -8WzxTbl759qIxt+z7aF7SkwJLJAAZS3qqCXKtMBo7ln4xKaoRLT2RohqD1YXGrg8 -wmYcUZoPpQKBgQCm2QVSuZ8pH0oFNjfMQbT0wbYJnd/lKMXBu4M1f9Ky4gHT0GYM -Ttirs6f6byfrduvmv2TpmWscsti80SktZywnE7fssMlqTHKzyFB9FBV2sFLHyyUr -gGFeK9xbsKgbeVkuTPdNKXvtv/eSd/XU38jIB/opQadGtY+ZBqWyfxb8AQKBgBLc -SlmBzZ/llSr7xdhn4ihG69hYQfacpL13r/hSCqinUDRuWLY5ynLacR8FYdY1pyzr -Yn6k6bPfU93QA0fLgG5ngK1SntMbBrIwWa0UqS+Cb+zhhd3xIUF1m8CmbibKCrTU -1vKaPnaAzqJZclFv9uN2hLdp9IO8cyzgZRpn9TzNAoGAUfZF1983qknfBgD8Lgm3 -zzKYtc8q2Ukatfo4VCp66CEprbLcBq5mKx6JiBoMGqU8SI5XVG0F0aHH2n8gImcu -bO0vtEldDc1ylZ/H7xhHFWlMzmTlsbHdHVtetFfKLTpjq6duvgLA12lJNHNVu3OU -Z1bRWDeZIP70+jdYrmSoVi8= ------END PRIVATE KEY----- diff --git a/test/src/old_slapdtest/certs/client.pem b/test/src/old_slapdtest/certs/client.pem deleted file mode 100644 index ca2989ca..00000000 --- a/test/src/old_slapdtest/certs/client.pem +++ /dev/null @@ -1,83 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 3 (0x3) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA - Validity - Not Before: Apr 12 18:52:38 2019 GMT - Not After : Mar 1 18:52:38 3019 GMT - Subject: C=DE, O=python-ldap, OU=slapd-test, CN=client - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:e3:b7:93:ba:9d:1a:e7:01:63:e6:4f:42:6f:44: - b0:47:31:c9:65:97:dd:88:58:5f:ce:e0:61:1d:70: - 9e:5f:6c:1c:4f:dc:1c:4a:d0:3b:fe:e9:a4:5c:47: - b6:f6:a3:47:fa:7e:4f:db:80:d7:a9:5d:fb:ff:31: - 70:d0:70:59:44:49:b7:e6:50:d1:ac:84:59:99:af: - 4b:23:3e:b6:0f:0f:54:74:e9:36:6c:fb:fe:d1:b0: - c5:e7:c7:57:3b:f6:61:46:11:0e:30:ab:30:ce:04: - 72:90:c5:e0:e1:34:99:3e:05:c1:34:40:00:6c:a8: - 5f:00:03:65:c2:db:f7:4a:d7:84:02:96:32:8b:0b: - ce:24:d7:34:c5:4e:78:7c:76:e3:76:78:06:a5:68: - f4:05:14:ea:23:e0:09:e8:9d:49:ec:c0:9e:39:cc: - 8c:79:57:60:5d:1b:ea:2a:3b:97:70:a3:f7:04:ba: - 42:a0:00:42:fc:4f:62:09:f4:2a:a5:9a:1d:ac:3d: - b3:2a:60:2f:1c:38:3a:ba:61:cb:a0:33:39:f1:ad: - f1:d5:e4:b3:53:85:f9:2f:c0:c7:b6:eb:eb:ef:eb: - ca:fa:e7:36:a5:3c:3c:b4:33:e7:14:31:33:32:05: - 56:38:4c:bd:df:7a:3a:e5:a1:8d:ad:60:c8:4f:24: - 87:25 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Key Usage: critical - Digital Signature - X509v3 Extended Key Usage: critical - TLS Web Client Authentication - X509v3 Subject Key Identifier: - 4F:E7:35:C7:C8:C1:01:C3:7C:53:86:B9:BF:AE:8B:D6:45:A2:78:20 - X509v3 Authority Key Identifier: - keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 - - Signature Algorithm: sha256WithRSAEncryption - 1c:90:5f:cf:18:48:95:4d:9d:d3:8e:6d:d1:69:19:1e:7b:3f: - 1f:48:7c:c8:0d:2f:c4:53:0f:89:23:f4:be:ea:b4:7a:c6:dd: - cc:18:0f:e7:34:ea:2c:d4:07:0d:65:78:e8:20:40:3f:36:ef: - 2c:00:31:69:e6:20:48:65:be:57:03:0e:69:ff:b9:83:59:99: - 7d:4d:86:98:14:5b:8e:39:25:3a:a8:6d:51:dc:45:a5:0f:cd: - f3:7a:fd:55:af:5f:55:75:20:03:f5:4a:75:6a:79:2f:76:84: - f6:4e:3d:1d:59:45:9a:b1:6a:57:6f:16:76:76:f8:df:6e:96: - d5:25:27:34:4b:21:d8:c9:9a:36:55:45:a0:43:16:43:68:93: - 37:af:81:89:06:d1:56:1b:9e:0f:62:40:ad:3c:4c:f5:ef:6c: - a2:a4:7f:f2:fa:78:9c:0d:c0:19:f1:10:e8:d8:cf:03:67:3c: - 2d:4d:f3:5d:67:5c:41:a7:4f:d6:c5:0e:ff:2c:04:dd:23:bb: - 85:44:8e:25:ac:15:a3:82:fa:a4:4f:fa:1d:87:f0:58:dc:ae: - 53:05:b9:81:e8:cb:e5:0c:ac:a5:74:68:03:f9:22:a0:45:b6: - 62:58:e0:98:d9:8c:54:a4:22:03:7a:37:12:eb:7d:b1:ad:45: - 60:8e:7a:df ------BEGIN CERTIFICATE----- -MIIDlDCCAnygAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU -MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz -MDExODUyMzhaMEkxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET -MBEGA1UECwwKc2xhcGQtdGVzdDEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA47eTup0a5wFj5k9Cb0SwRzHJZZfdiFhfzuBh -HXCeX2wcT9wcStA7/umkXEe29qNH+n5P24DXqV37/zFw0HBZREm35lDRrIRZma9L -Iz62Dw9UdOk2bPv+0bDF58dXO/ZhRhEOMKswzgRykMXg4TSZPgXBNEAAbKhfAANl -wtv3SteEApYyiwvOJNc0xU54fHbjdngGpWj0BRTqI+AJ6J1J7MCeOcyMeVdgXRvq -KjuXcKP3BLpCoABC/E9iCfQqpZodrD2zKmAvHDg6umHLoDM58a3x1eSzU4X5L8DH -tuvr7+vK+uc2pTw8tDPnFDEzMgVWOEy933o65aGNrWDITySHJQIDAQABo3gwdjAM -BgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEF -BQcDAjAdBgNVHQ4EFgQUT+c1x8jBAcN8U4a5v66L1kWieCAwHwYDVR0jBBgwFoAU -vXjVSvGQlsXo7GZJI0cDXyZzhrIwDQYJKoZIhvcNAQELBQADggEBAByQX88YSJVN -ndOObdFpGR57Px9IfMgNL8RTD4kj9L7qtHrG3cwYD+c06izUBw1leOggQD827ywA -MWnmIEhlvlcDDmn/uYNZmX1NhpgUW445JTqobVHcRaUPzfN6/VWvX1V1IAP1SnVq -eS92hPZOPR1ZRZqxaldvFnZ2+N9ultUlJzRLIdjJmjZVRaBDFkNokzevgYkG0VYb -ng9iQK08TPXvbKKkf/L6eJwNwBnxEOjYzwNnPC1N811nXEGnT9bFDv8sBN0ju4VE -jiWsFaOC+qRP+h2H8FjcrlMFuYHoy+UMrKV0aAP5IqBFtmJY4JjZjFSkIgN6NxLr -fbGtRWCOet8= ------END CERTIFICATE----- diff --git a/test/src/old_slapdtest/certs/gencerts.sh b/test/src/old_slapdtest/certs/gencerts.sh deleted file mode 100755 index 8a99db58..00000000 --- a/test/src/old_slapdtest/certs/gencerts.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh -# Written by Christian Heimes -set -e - -export CAOUTDIR=. -export CATMPDIR=tmp - -rm -rf $CATMPDIR -rm -rf ca.pem ca.key server.pem server.key client.pem client.key -rm -rf cert9.db key4.db pkcs11.tx - -mkdir -p $CAOUTDIR -mkdir -p $CATMPDIR - -touch $CATMPDIR/ca.db -touch $CATMPDIR/ca.db.attr -echo '01' > $CATMPDIR/ca.crt.srl -echo '01' > $CATMPDIR/ca.crl.srl - -# root CA -openssl req -new \ - -config ca.conf \ - -out $CATMPDIR/ca.csr \ - -keyout $CAOUTDIR/ca.key \ - -batch - -openssl ca -selfsign \ - -config ca.conf \ - -in $CATMPDIR/ca.csr \ - -out $CAOUTDIR/ca.pem \ - -extensions ca_ext \ - -days 356300 \ - -batch - -# server cert -openssl req -new \ - -config server.conf \ - -out $CATMPDIR/server.csr \ - -keyout $CAOUTDIR/server.key \ - -batch - -openssl ca \ - -config ca.conf \ - -in $CATMPDIR/server.csr \ - -out $CAOUTDIR/server.pem \ - -policy match_pol \ - -extensions server_ext \ - -batch - -# client cert -openssl req -new \ - -config client.conf \ - -out $CATMPDIR/client.csr \ - -keyout $CAOUTDIR/client.key \ - -batch - -openssl ca \ - -config ca.conf \ - -in $CATMPDIR/client.csr \ - -out $CAOUTDIR/client.pem \ - -policy match_pol \ - -extensions client_ext \ - -batch - -# cleanup -rm -rf $CATMPDIR ca.key - -echo DONE diff --git a/test/src/old_slapdtest/certs/gennssdb.sh b/test/src/old_slapdtest/certs/gennssdb.sh deleted file mode 100755 index aeeb3331..00000000 --- a/test/src/old_slapdtest/certs/gennssdb.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# Written by Christian Heimes -set -e - -CATMPDIR=tmp -PASSFILE=${CATMPDIR}/passwd.txt -NSSDB=sql:${CAOUTDIR} - -mkdir -p $CATMPDIR - -# Create PKCS#12 files for NSSDB import -echo "dummy" > $PASSFILE -openssl pkcs12 -name "servercert" -in server.pem -inkey server.key \ - -caname "testca" -CAfile ca.pem \ - -password "file:${PASSFILE}" -export -out server.p12 -openssl pkcs12 -name "clientcert" -in client.pem -inkey client.key \ - -caname "testca" -CAfile ca.pem \ - -password "file:${PASSFILE}" -export -out client.p12 - -# Create NSS DB -certutil -d $NSSDB -N --empty-password -certutil -d $NSSDB -A -n "testca" -t CT,, -a -i ca.pem -pk12util -d $NSSDB -i server.p12 -w ${PASSFILE} -pk12util -d $NSSDB -i client.p12 -w ${PASSFILE} -certutil -d $NSSDB -L - -# cleanup -rm -rf $CATMPDIR server.p12 client.p12 \ No newline at end of file diff --git a/test/src/old_slapdtest/certs/server.conf b/test/src/old_slapdtest/certs/server.conf deleted file mode 100644 index 94f4427a..00000000 --- a/test/src/old_slapdtest/certs/server.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Written by Christian Heimes - -[req] -default_bits = 2048 -encrypt_key = no -default_md = sha256 -utf8 = yes -string_mask = utf8only -prompt = no -distinguished_name = server_dn - -[server_dn] -countryName = "DE" -organizationName = "python-ldap" -organizationalUnitName = "slapd-test" -commonName = "server cert for localhost" diff --git a/test/src/old_slapdtest/certs/server.key b/test/src/old_slapdtest/certs/server.key deleted file mode 100644 index a8916701..00000000 --- a/test/src/old_slapdtest/certs/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsBk0ml3ERFJyg -I6ujIJYERVU4doTZZd4r4z/LOef0hyiYiIQAc9wetaoZpM+bl4Eherxy9SBaCBwR -zefbaYQz2f2hdEDb+sISOiTke1eiF2ugYNlS55Wk1KnCnORE9bjcSNLPsscoUSzE -2bnBSoUwdiVK18YOCZR6GTeC8eA3ekvlR+9g+FBOgQ9+StXPDdq+iIAGXZREJIua -munErtTOw85De4YFCnzGw3UeCITDD4wFmI2IWphRFwWPsSDwUJfATA8S+7Rm4vwr -Qj726gUDlicTzPXKhJjXjj6XL7xXHfpQwMPkBCrxesKceHMJ+mrRsuuqHciuixRi -g94mILElAgMBAAECggEADG5oJOHMye8zYl8xiBhSvvxDrFDkSNGTvJgvhAArQwCB -boRvBZlZzt5R7Ih8eEH6kvDLrYMJU3hCjwbSOojlhNm7+m7sQPleDPMmt1wyeQQ4 -Qt681cDmj4LOwcGUvWcEdObOVTQWMFOtaIxTYCSCe34OM9pj9Z+7mxc3a78O9PND -Ib/CwcTA1OyoupzkKirqkdLXwK3x2aT/1TMaPX94taHB51cxXc7AglL9QnuCkuaG -krqrexy3rGimzsP3OwQGEUjWKcZVSSPT8/k1pPE9hRgOqBy05BfkAzlebdvc3GO5 -AbZk0NX2sfVHl4dTEXs/hTBCTQ3XmaltumQ9MdL+AQKBgQDg2I5QxBA2UHb8vCtK -f31kfG6YQc4MkoslrrMrtJjZqDYaLZPS1ARPSfYRqcc+7GDreuLmw39f8ZECd+2W -BYUqzZv9g13R9DY99g0/sINnZGsESwfIdLNNlHvVx2UrD5ybCj4vLhuPsVV7XlWs -cpl+rcuBVpqy8UIXifQ/Z3xLvwKBgQDD3CLjuC0mcTO2sIWqEHqVkc8CY2NJA2Qh -C78fwpaCqJUUdWnS69QbRGWgkFJL+oO8lQVQ1bXhZLHyQmy7Z5d5olCH6AW4GRnf -hBAnKJ+QTm9B6QVWzjUuHuOeCukfiTQbha14pOS9ar3X2QFWjDnzCRrnAxJmoY3H -BJATLHhMGwKBgQDSxAy7xt4Pm+O9y8Gk5tcq771X+i9k96V54EZRzMuPFDAK3/h2 -o4marZD9Q7Hi2P+NHTc+67klvbKZpsPOYkRPOEdmH9M9cPe7oz8OGa9DpwzuDEsy -a7p8GZjvbyb1c3/wkWxzG3x4eNnReD9FFHOwHMfr6LvAy4iRuh57pM0NzwKBgDY3 -1DixnV4M7EHgb7/6O9T3vhRtKujlVWyIcen61etpe4tkTV0kB11c+70M9pstyBYG -MqiD4It6coAbvznJnXcAZcaZhivGVxE237nXVwR9kfLu7JlxD+uqhVwUrSAbvR75 -TGIfU2rUB6We3u30d349wQK+KPPcOQEk1DValBqNAoGBAKfXOXgFBkIVW79fOkup -aIZXdEmU3Up61Oo0KDbxsg4l73NnnvuEnNMBTx3nT3KCVIAcQL9MNpLX/Z0HjOn1 -aiWVtTNq2OFL0V0HueBhbkFiWp551jTS7LjndCYHpUB/B8/wXP0kxHUm8HrQrRvK -DhV3zcxsXts1INidXjzzOkPi ------END PRIVATE KEY----- diff --git a/test/src/old_slapdtest/certs/server.pem b/test/src/old_slapdtest/certs/server.pem deleted file mode 100644 index 25ba06c0..00000000 --- a/test/src/old_slapdtest/certs/server.pem +++ /dev/null @@ -1,86 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA - Validity - Not Before: Apr 12 18:52:38 2019 GMT - Not After : Mar 1 18:52:38 3019 GMT - Subject: C=DE, O=python-ldap, OU=slapd-test, CN=server cert for localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:ac:06:4d:26:97:71:11:14:9c:a0:23:ab:a3:20: - 96:04:45:55:38:76:84:d9:65:de:2b:e3:3f:cb:39: - e7:f4:87:28:98:88:84:00:73:dc:1e:b5:aa:19:a4: - cf:9b:97:81:21:7a:bc:72:f5:20:5a:08:1c:11:cd: - e7:db:69:84:33:d9:fd:a1:74:40:db:fa:c2:12:3a: - 24:e4:7b:57:a2:17:6b:a0:60:d9:52:e7:95:a4:d4: - a9:c2:9c:e4:44:f5:b8:dc:48:d2:cf:b2:c7:28:51: - 2c:c4:d9:b9:c1:4a:85:30:76:25:4a:d7:c6:0e:09: - 94:7a:19:37:82:f1:e0:37:7a:4b:e5:47:ef:60:f8: - 50:4e:81:0f:7e:4a:d5:cf:0d:da:be:88:80:06:5d: - 94:44:24:8b:9a:9a:e9:c4:ae:d4:ce:c3:ce:43:7b: - 86:05:0a:7c:c6:c3:75:1e:08:84:c3:0f:8c:05:98: - 8d:88:5a:98:51:17:05:8f:b1:20:f0:50:97:c0:4c: - 0f:12:fb:b4:66:e2:fc:2b:42:3e:f6:ea:05:03:96: - 27:13:cc:f5:ca:84:98:d7:8e:3e:97:2f:bc:57:1d: - fa:50:c0:c3:e4:04:2a:f1:7a:c2:9c:78:73:09:fa: - 6a:d1:b2:eb:aa:1d:c8:ae:8b:14:62:83:de:26:20: - b1:25 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: critical - TLS Web Server Authentication - X509v3 Subject Key Identifier: - 08:D1:86:1B:82:0A:4F:71:31:E4:F5:31:23:CC:67:3B:FA:84:3B:A0 - X509v3 Authority Key Identifier: - keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 - - X509v3 Subject Alternative Name: - DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 - Signature Algorithm: sha256WithRSAEncryption - 88:60:af:be:11:c4:aa:dc:9b:f1:e7:14:da:20:aa:6f:2f:06: - ae:38:b2:7c:ac:90:81:22:51:7e:cb:26:15:6e:fe:67:98:c1: - 0d:dc:aa:39:98:2b:d2:cc:3c:ff:1a:92:2f:56:0a:a9:6e:d8: - 9a:3d:c5:4d:6f:cc:91:2e:e3:4e:bf:22:ab:cb:92:1a:a0:8f: - 43:cd:82:bc:48:55:c4:95:cf:10:6b:6a:31:19:92:7d:e0:06: - 05:6f:0b:33:e7:2a:37:42:f9:ec:1b:29:99:e1:58:0c:01:a7: - c3:8b:58:71:21:9f:61:8c:a7:fb:b6:7e:32:8b:a9:4e:c7:1f: - f6:46:e8:dd:ac:a6:4c:53:f8:4d:93:e4:ec:73:ab:0b:be:98: - c5:78:c4:92:c0:4c:78:47:52:2f:93:07:67:20:a4:5a:7f:59: - 7e:4f:48:53:20:0d:37:bb:06:f8:44:42:64:b4:94:15:43:d1: - 4c:51:f3:97:1d:2d:cd:db:b9:bb:1a:69:10:89:7d:ae:1d:0d: - 94:78:45:29:cd:c4:42:67:67:96:05:bf:da:aa:23:65:7b:04: - ff:b7:ac:9d:ee:0b:e7:0f:c1:c5:0b:48:fe:0f:d6:3f:d8:b4: - 77:12:bb:f5:91:4f:43:e6:01:3f:a4:c0:ea:8c:c6:68:99:8e: - 49:e8:c4:8b ------BEGIN CERTIFICATE----- -MIID1zCCAr+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU -MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz -MDExODUyMzhaMFwxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET -MBEGA1UECwwKc2xhcGQtdGVzdDEiMCAGA1UEAwwZc2VydmVyIGNlcnQgZm9yIGxv -Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKwGTSaXcREU -nKAjq6MglgRFVTh2hNll3ivjP8s55/SHKJiIhABz3B61qhmkz5uXgSF6vHL1IFoI -HBHN59tphDPZ/aF0QNv6whI6JOR7V6IXa6Bg2VLnlaTUqcKc5ET1uNxI0s+yxyhR -LMTZucFKhTB2JUrXxg4JlHoZN4Lx4Dd6S+VH72D4UE6BD35K1c8N2r6IgAZdlEQk -i5qa6cSu1M7DzkN7hgUKfMbDdR4IhMMPjAWYjYhamFEXBY+xIPBQl8BMDxL7tGbi -/CtCPvbqBQOWJxPM9cqEmNeOPpcvvFcd+lDAw+QEKvF6wpx4cwn6atGy66odyK6L -FGKD3iYgsSUCAwEAAaOBpzCBpDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF -oDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUCNGGG4IKT3Ex5PUx -I8xnO/qEO6AwHwYDVR0jBBgwFoAUvXjVSvGQlsXo7GZJI0cDXyZzhrIwLAYDVR0R -BCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3 -DQEBCwUAA4IBAQCIYK++EcSq3Jvx5xTaIKpvLwauOLJ8rJCBIlF+yyYVbv5nmMEN -3Ko5mCvSzDz/GpIvVgqpbtiaPcVNb8yRLuNOvyKry5IaoI9DzYK8SFXElc8Qa2ox -GZJ94AYFbwsz5yo3QvnsGymZ4VgMAafDi1hxIZ9hjKf7tn4yi6lOxx/2RujdrKZM -U/hNk+Tsc6sLvpjFeMSSwEx4R1IvkwdnIKRaf1l+T0hTIA03uwb4REJktJQVQ9FM -UfOXHS3N27m7GmkQiX2uHQ2UeEUpzcRCZ2eWBb/aqiNlewT/t6yd7gvnD8HFC0j+ -D9Y/2LR3Erv1kU9D5gE/pMDqjMZomY5J6MSL ------END CERTIFICATE----- diff --git a/test/test_actionsmap.py b/test/test_actionsmap.py index deaf52f5..a509b1da 100644 --- a/test/test_actionsmap.py +++ b/test/test_actionsmap.py @@ -188,8 +188,11 @@ def test_extra_argument_parser_add_argument_bad_arg(iface): with pytest.raises(MoulinetteError) as exception: extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": 1}) - translation = m18n.g("error") - expected_msg = translation.format() + expected_msg = "unable to validate extra parameter '%s' for argument '%s': %s" % ( + "ask", + "foo", + "parameter value must be a string, got 1", + ) assert expected_msg in str(exception) extra_argument_parse = ExtraArgumentParser(iface) @@ -258,15 +261,18 @@ def test_actions_map_import_error(mocker): def import_mock(name, globals={}, locals={}, fromlist=[], level=-1): if name == "moulitest.testauth": mocker.stopall() - raise ImportError + raise ImportError("Yoloswag") return orig_import(name, globals, locals, fromlist, level) mocker.patch("builtins.__import__", side_effect=import_mock) with pytest.raises(MoulinetteError) as exception: amap.process({}, timeout=30, route=("GET", "/test-auth/none")) - translation = m18n.g("error") - expected_msg = translation.format() + expected_msg = "unable to load function % s.%s because: %s" % ( + "moulitest", + "testauth_none", + "Yoloswag", + ) assert expected_msg in str(exception) diff --git a/test/test_auth.py b/test/test_auth.py index a7a79c90..624ebccd 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -329,8 +329,7 @@ class TestAuthCLI: assert "12345" in message.out - mocker.patch("sys.exit") - with pytest.raises(MoulinetteError): + with pytest.raises(SystemExit): moulinette_cli.run( ["testauth", "with_type_int", "yoloswag"], output_as="plain" ) diff --git a/test/test_ldap.py b/test/test_ldap.py index 68a7aae1..98b57447 100644 --- a/test/test_ldap.py +++ b/test/test_ldap.py @@ -101,16 +101,16 @@ class TestLDAP: admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0] assert "cn" in admin_info - assert admin_info["cn"] == ["admin".encode("utf-8")] + assert admin_info["cn"] == ["admin"] assert "description" in admin_info - assert admin_info["description"] == ["LDAP Administrator".encode("utf-8")] + assert admin_info["description"] == ["LDAP Administrator"] assert "userPassword" in admin_info assert admin_info["userPassword"][0].startswith("{CRYPT}$6$") admin_info = ldap_interface.search( "cn=admin,dc=yunohost,dc=org", attrs=["userPassword"] )[0] - assert admin_info.keys() == ["userPassword".encode("utf-8")] + assert list(admin_info.keys()) == ["userPassword"] assert admin_info["userPassword"][0].startswith("{CRYPT}$6$") def test_sasl_read(self, ldap_server): @@ -122,16 +122,16 @@ class TestLDAP: admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0] assert "cn" in admin_info - assert admin_info["cn"] == ["admin".encode("utf-8")] + assert admin_info["cn"] == ["admin"] assert "description" in admin_info - assert admin_info["description"] == ["LDAP Administrator".encode("utf-8")] + assert admin_info["description"] == ["LDAP Administrator"] assert "userPassword" in admin_info assert admin_info["userPassword"][0].startswith("{CRYPT}$6$") admin_info = ldap_interface.search( "cn=admin,dc=yunohost,dc=org", attrs=["userPassword"] )[0] - assert admin_info.keys() == ["userPassword".encode("utf-8")] + assert list(admin_info.keys()) == ["userPassword"] assert admin_info["userPassword"][0].startswith("{CRYPT}$6$") def test_anonymous_read(self, ldap_server): @@ -140,9 +140,9 @@ class TestLDAP: admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0] assert "cn" in admin_info - assert admin_info["cn"] == ["admin".encode("utf-8")] + assert admin_info["cn"] == ["admin"] assert "description" in admin_info - assert admin_info["description"] == ["LDAP Administrator".encode("utf-8")] + assert admin_info["description"] == ["LDAP Administrator"] assert "userPassword" not in admin_info admin_info = ldap_interface.search( @@ -180,11 +180,11 @@ class TestLDAP: new_user_info = self.add_new_user(ldap_interface) assert "cn" in new_user_info - assert new_user_info["cn"] == ["new_user".encode("utf-8")] + assert new_user_info["cn"] == ["new_user"] assert "sn" in new_user_info - assert new_user_info["sn"] == ["new_user".encode("utf-8")] + assert new_user_info["sn"] == ["new_user"] assert "uid" in new_user_info - assert new_user_info["uid"] == ["new_user".encode("utf-8")] + assert new_user_info["uid"] == ["new_user"] assert "objectClass" in new_user_info assert "inetOrgPerson" in new_user_info["objectClass"] assert "posixAccount" in new_user_info["objectClass"] @@ -198,11 +198,11 @@ class TestLDAP: new_user_info = self.add_new_user(ldap_interface) assert "cn" in new_user_info - assert new_user_info["cn"] == ["new_user".encode("utf-8")] + assert new_user_info["cn"] == ["new_user"] assert "sn" in new_user_info - assert new_user_info["sn"] == ["new_user".encode("utf-8")] + assert new_user_info["sn"] == ["new_user"] assert "uid" in new_user_info - assert new_user_info["uid"] == ["new_user".encode("utf-8")] + assert new_user_info["uid"] == ["new_user"] assert "objectClass" in new_user_info assert "inetOrgPerson" in new_user_info["objectClass"] assert "posixAccount" in new_user_info["objectClass"] diff --git a/test/test_process.py b/test/test_process.py index 4b0c1acd..b3af92ff 100644 --- a/test/test_process.py +++ b/test/test_process.py @@ -65,58 +65,61 @@ def test_run_shell_kwargs(): def test_call_async_output(test_file): - def callback(a): - assert a == "foo\n" or a == "bar\n" + def stdout_callback(a): + assert a == "foo" or a == "bar" - call_async_output(["cat", str(test_file)], callback) + def stderr_callback(a): + assert False # we shouldn't reach this line - with pytest.raises(ValueError): + callbacks = (lambda l: stdout_callback(l), lambda l: stderr_callback(l)) + + call_async_output(["cat", str(test_file)], callbacks) + + with pytest.raises(TypeError): call_async_output(["cat", str(test_file)], 1) def callbackA(a): - assert a == "foo\n" or a == "bar\n" + assert a == "foo" or a == "bar" def callbackB(a): - pass + assert "cat: doesntexists" in a callback = (callbackA, callbackB) call_async_output(["cat", str(test_file)], callback) + call_async_output(["cat", "doesntexists"], callback) def test_call_async_output_kwargs(test_file, mocker): - def callback(a): - assert a == "foo\n" or a == "bar\n" + def stdinfo_callback(a): + assert False # we shouldn't reach this line + + def stdout_callback(a): + assert a == "foo" or a == "bar" + + def stderr_callback(a): + assert False # we shouldn't reach this line + + callbacks = ( + lambda l: stdout_callback(l), + lambda l: stderr_callback(l), + lambda l: stdinfo_callback(l), + ) with pytest.raises(ValueError): - call_async_output(["cat", str(test_file)], callback, stdout=None) + call_async_output(["cat", str(test_file)], callbacks, stdout=None) with pytest.raises(ValueError): - call_async_output(["cat", str(test_file)], callback, stderr=None) - - call_async_output(["cat", str(test_file)], callback, stdinfo=None) - - def callbackA(a): - assert a == "foo\n" or a == "bar\n" - - def callbackB(a): - pass - - def callbackC(a): - pass - - callback = (callbackA, callbackB, callbackC) + call_async_output(["cat", str(test_file)], callbacks, stderr=None) + with pytest.raises(TypeError): + call_async_output(["cat", str(test_file)], callbacks, stdinfo=None) dirname = os.path.dirname(str(test_file)) - os.mkdir(os.path.join(dirname, "teststdinfo")) + os.mkdir(os.path.join(dirname, "testcwd")) call_async_output( - ["cat", str(test_file)], - callback, - stdinfo=os.path.join(dirname, "teststdinfo", "teststdinfo"), + ["cat", str(test_file)], callbacks, cwd=os.path.join(dirname, "testcwd") ) def test_check_output(test_file): - assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar".encode( - "utf-8" - ) + assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar" - assert check_output("cat %s" % str(test_file)) == "foo\nbar".encode("utf-8") + assert check_output("cat %s" % str(test_file)) == "foo\nbar" diff --git a/tox.ini b/tox.ini index 770704ae..67e9d9e0 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,7 @@ skip_install = {[testenv:format]skip_install} usedevelop = {[testenv:format]usedevelop} [testenv:docs] +basepython = {[testenv:format]basepython} usedevelop = True commands = python3 -m sphinx -W doc/ doc/_build deps =