mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Merge branch 'dev' into fix-argparse
This commit is contained in:
commit
1a95e6e779
26 changed files with 80 additions and 1263 deletions
|
@ -7,9 +7,6 @@ addons:
|
||||||
- slapd
|
- slapd
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
|
||||||
- env: TOXENV=py37-pytest
|
|
||||||
- env: TOXENV=py37-lint
|
|
||||||
include:
|
include:
|
||||||
- python: 3.7
|
- python: 3.7
|
||||||
env: TOXENV=py37-pytest
|
env: TOXENV=py37-pytest
|
||||||
|
@ -17,7 +14,7 @@ matrix:
|
||||||
env: TOXENV=py37-lint
|
env: TOXENV=py37-lint
|
||||||
- python: 3.7
|
- python: 3.7
|
||||||
env: TOXENV=format-check
|
env: TOXENV=format-check
|
||||||
- python: 3.5
|
- python: 3.7
|
||||||
env: TOXENV=docs
|
env: TOXENV=docs
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|
|
@ -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:
|
And here is its docstring:
|
||||||
|
|
||||||
.. automethod:: moulinette.authenticators.ldap.Authenticator.update
|
.. automethod:: moulinette.authenticators.ldap.Authenticator.validate_uniqueness
|
||||||
|
|
||||||
Get conflict
|
Get conflict
|
||||||
============
|
============
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Stream operation utils
|
Stream operation utils
|
||||||
======================
|
======================
|
||||||
|
|
||||||
.. autofunction:: moulinette.utils.stream.async_file_reading
|
.. autofunction:: moulinette.utils.stream.LogPipe
|
||||||
|
|
|
@ -130,7 +130,7 @@ class BaseAuthenticator(object):
|
||||||
s_id, s_token = token
|
s_id, s_token = token
|
||||||
# Attempt to authenticate
|
# Attempt to authenticate
|
||||||
self._authenticate_session(s_id, s_token)
|
self._authenticate_session(s_id, s_token)
|
||||||
except MoulinetteError as e:
|
except MoulinetteError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|
|
@ -558,7 +558,11 @@ class HTTPBadRequestResponse(HTTPResponse):
|
||||||
if isinstance(error, MoulinetteError):
|
if isinstance(error, MoulinetteError):
|
||||||
content = error.content()
|
content = error.content()
|
||||||
if isinstance(content, dict):
|
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:
|
else:
|
||||||
super(HTTPBadRequestResponse, self).__init__(content, 400)
|
super(HTTPBadRequestResponse, self).__init__(content, 400)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -72,11 +72,18 @@ def call_async_output(args, callback, **kwargs):
|
||||||
kwargs["env"] = os.environ
|
kwargs["env"] = os.environ
|
||||||
kwargs["env"]["YNH_STDINFO"] = str(stdinfo.fdWrite)
|
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["stdout"].close()
|
||||||
kwargs["stderr"].close()
|
kwargs["stderr"].close()
|
||||||
if stdinfo:
|
if stdinfo:
|
||||||
stdinfo.close()
|
stdinfo.close()
|
||||||
|
raise
|
||||||
|
|
||||||
# on slow hardware, in very edgy situations it is possible that the process
|
# 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
|
# isn't finished just after having closed stdout and stderr, so we wait a
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
try:
|
import slapdtest
|
||||||
import slapdtest
|
|
||||||
except ImportError:
|
|
||||||
import old_slapdtest as slapdtest
|
|
||||||
import os
|
import os
|
||||||
from moulinette.authenticators import ldap as m_ldap
|
from moulinette.authenticators import ldap as m_ldap
|
||||||
|
|
||||||
|
@ -60,7 +57,7 @@ class LDAPServer:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
with open(os.path.join(HERE, "..", "ldap_files", "ldap_scheme.yml"), "rb") as f:
|
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():
|
def _get_ldap_interface():
|
||||||
conf = {
|
conf = {
|
||||||
|
|
|
@ -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
|
|
|
@ -1,2 +0,0 @@
|
||||||
# flake8: noqa
|
|
||||||
from ._slapdtest import SlapdObject
|
|
|
@ -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()
|
|
|
@ -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.
|
|
|
@ -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
|
|
|
@ -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-----
|
|
|
@ -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"
|
|
|
@ -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-----
|
|
|
@ -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-----
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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"
|
|
|
@ -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-----
|
|
|
@ -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-----
|
|
|
@ -188,8 +188,11 @@ def test_extra_argument_parser_add_argument_bad_arg(iface):
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": 1})
|
extra_argument_parse.add_argument(GLOBAL_SECTION, "foo", {"ask": 1})
|
||||||
|
|
||||||
translation = m18n.g("error")
|
expected_msg = "unable to validate extra parameter '%s' for argument '%s': %s" % (
|
||||||
expected_msg = translation.format()
|
"ask",
|
||||||
|
"foo",
|
||||||
|
"parameter value must be a string, got 1",
|
||||||
|
)
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
extra_argument_parse = ExtraArgumentParser(iface)
|
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):
|
def import_mock(name, globals={}, locals={}, fromlist=[], level=-1):
|
||||||
if name == "moulitest.testauth":
|
if name == "moulitest.testauth":
|
||||||
mocker.stopall()
|
mocker.stopall()
|
||||||
raise ImportError
|
raise ImportError("Yoloswag")
|
||||||
return orig_import(name, globals, locals, fromlist, level)
|
return orig_import(name, globals, locals, fromlist, level)
|
||||||
|
|
||||||
mocker.patch("builtins.__import__", side_effect=import_mock)
|
mocker.patch("builtins.__import__", side_effect=import_mock)
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
amap.process({}, timeout=30, route=("GET", "/test-auth/none"))
|
amap.process({}, timeout=30, route=("GET", "/test-auth/none"))
|
||||||
|
|
||||||
translation = m18n.g("error")
|
expected_msg = "unable to load function % s.%s because: %s" % (
|
||||||
expected_msg = translation.format()
|
"moulitest",
|
||||||
|
"testauth_none",
|
||||||
|
"Yoloswag",
|
||||||
|
)
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -329,8 +329,7 @@ class TestAuthCLI:
|
||||||
|
|
||||||
assert "12345" in message.out
|
assert "12345" in message.out
|
||||||
|
|
||||||
mocker.patch("sys.exit")
|
with pytest.raises(SystemExit):
|
||||||
with pytest.raises(MoulinetteError):
|
|
||||||
moulinette_cli.run(
|
moulinette_cli.run(
|
||||||
["testauth", "with_type_int", "yoloswag"], output_as="plain"
|
["testauth", "with_type_int", "yoloswag"], output_as="plain"
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,16 +101,16 @@ class TestLDAP:
|
||||||
|
|
||||||
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
assert "cn" in admin_info
|
assert "cn" in admin_info
|
||||||
assert admin_info["cn"] == ["admin".encode("utf-8")]
|
assert admin_info["cn"] == ["admin"]
|
||||||
assert "description" in admin_info
|
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 "userPassword" in admin_info
|
||||||
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
admin_info = ldap_interface.search(
|
admin_info = ldap_interface.search(
|
||||||
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
||||||
)[0]
|
)[0]
|
||||||
assert admin_info.keys() == ["userPassword".encode("utf-8")]
|
assert list(admin_info.keys()) == ["userPassword"]
|
||||||
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
def test_sasl_read(self, ldap_server):
|
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]
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
assert "cn" in admin_info
|
assert "cn" in admin_info
|
||||||
assert admin_info["cn"] == ["admin".encode("utf-8")]
|
assert admin_info["cn"] == ["admin"]
|
||||||
assert "description" in admin_info
|
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 "userPassword" in admin_info
|
||||||
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
admin_info = ldap_interface.search(
|
admin_info = ldap_interface.search(
|
||||||
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
"cn=admin,dc=yunohost,dc=org", attrs=["userPassword"]
|
||||||
)[0]
|
)[0]
|
||||||
assert admin_info.keys() == ["userPassword".encode("utf-8")]
|
assert list(admin_info.keys()) == ["userPassword"]
|
||||||
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
assert admin_info["userPassword"][0].startswith("{CRYPT}$6$")
|
||||||
|
|
||||||
def test_anonymous_read(self, ldap_server):
|
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]
|
admin_info = ldap_interface.search("cn=admin,dc=yunohost,dc=org", attrs=None)[0]
|
||||||
assert "cn" in admin_info
|
assert "cn" in admin_info
|
||||||
assert admin_info["cn"] == ["admin".encode("utf-8")]
|
assert admin_info["cn"] == ["admin"]
|
||||||
assert "description" in admin_info
|
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
|
assert "userPassword" not in admin_info
|
||||||
|
|
||||||
admin_info = ldap_interface.search(
|
admin_info = ldap_interface.search(
|
||||||
|
@ -180,11 +180,11 @@ class TestLDAP:
|
||||||
|
|
||||||
new_user_info = self.add_new_user(ldap_interface)
|
new_user_info = self.add_new_user(ldap_interface)
|
||||||
assert "cn" in new_user_info
|
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 "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 "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 "objectClass" in new_user_info
|
||||||
assert "inetOrgPerson" in new_user_info["objectClass"]
|
assert "inetOrgPerson" in new_user_info["objectClass"]
|
||||||
assert "posixAccount" 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)
|
new_user_info = self.add_new_user(ldap_interface)
|
||||||
assert "cn" in new_user_info
|
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 "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 "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 "objectClass" in new_user_info
|
||||||
assert "inetOrgPerson" in new_user_info["objectClass"]
|
assert "inetOrgPerson" in new_user_info["objectClass"]
|
||||||
assert "posixAccount" in new_user_info["objectClass"]
|
assert "posixAccount" in new_user_info["objectClass"]
|
||||||
|
|
|
@ -65,58 +65,61 @@ def test_run_shell_kwargs():
|
||||||
|
|
||||||
|
|
||||||
def test_call_async_output(test_file):
|
def test_call_async_output(test_file):
|
||||||
def callback(a):
|
def stdout_callback(a):
|
||||||
assert a == "foo\n" or a == "bar\n"
|
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)
|
call_async_output(["cat", str(test_file)], 1)
|
||||||
|
|
||||||
def callbackA(a):
|
def callbackA(a):
|
||||||
assert a == "foo\n" or a == "bar\n"
|
assert a == "foo" or a == "bar"
|
||||||
|
|
||||||
def callbackB(a):
|
def callbackB(a):
|
||||||
pass
|
assert "cat: doesntexists" in a
|
||||||
|
|
||||||
callback = (callbackA, callbackB)
|
callback = (callbackA, callbackB)
|
||||||
call_async_output(["cat", str(test_file)], callback)
|
call_async_output(["cat", str(test_file)], callback)
|
||||||
|
call_async_output(["cat", "doesntexists"], callback)
|
||||||
|
|
||||||
|
|
||||||
def test_call_async_output_kwargs(test_file, mocker):
|
def test_call_async_output_kwargs(test_file, mocker):
|
||||||
def callback(a):
|
def stdinfo_callback(a):
|
||||||
assert a == "foo\n" or a == "bar\n"
|
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):
|
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):
|
with pytest.raises(ValueError):
|
||||||
call_async_output(["cat", str(test_file)], callback, stderr=None)
|
call_async_output(["cat", str(test_file)], callbacks, stderr=None)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
call_async_output(["cat", str(test_file)], callback, stdinfo=None)
|
call_async_output(["cat", str(test_file)], callbacks, 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)
|
|
||||||
|
|
||||||
dirname = os.path.dirname(str(test_file))
|
dirname = os.path.dirname(str(test_file))
|
||||||
os.mkdir(os.path.join(dirname, "teststdinfo"))
|
os.mkdir(os.path.join(dirname, "testcwd"))
|
||||||
call_async_output(
|
call_async_output(
|
||||||
["cat", str(test_file)],
|
["cat", str(test_file)], callbacks, cwd=os.path.join(dirname, "testcwd")
|
||||||
callback,
|
|
||||||
stdinfo=os.path.join(dirname, "teststdinfo", "teststdinfo"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_check_output(test_file):
|
def test_check_output(test_file):
|
||||||
assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar".encode(
|
assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar"
|
||||||
"utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert check_output("cat %s" % str(test_file)) == "foo\nbar".encode("utf-8")
|
assert check_output("cat %s" % str(test_file)) == "foo\nbar"
|
||||||
|
|
1
tox.ini
1
tox.ini
|
@ -32,6 +32,7 @@ skip_install = {[testenv:format]skip_install}
|
||||||
usedevelop = {[testenv:format]usedevelop}
|
usedevelop = {[testenv:format]usedevelop}
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
|
basepython = {[testenv:format]basepython}
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
commands = python3 -m sphinx -W doc/ doc/_build
|
commands = python3 -m sphinx -W doc/ doc/_build
|
||||||
deps =
|
deps =
|
||||||
|
|
Loading…
Add table
Reference in a new issue