Skip to content

Commit

Permalink
Make LDAPI optional
Browse files Browse the repository at this point in the history
LDAPI is LDAP over Unix Domain Socket, also referred as LDAP IPC.
AF_UNIX is only supported on POSIX compatible operating systems. Don't
bind to LDAPI socket and skip LDAPI specific tests, e.g. SASL EXTERNAL
with SO_PEERCRED.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
  • Loading branch information
Christian Heimes committed Dec 18, 2017
1 parent ab93063 commit aa0a07b
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 21 deletions.
3 changes: 2 additions & 1 deletion Lib/slapdtest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
__version__ = '3.0.0b2'

from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler
from slapdtest._slapdtest import skip_unless_ci, requires_sasl, requires_tls
from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls
from slapdtest._slapdtest import skip_unless_ci
47 changes: 38 additions & 9 deletions Lib/slapdtest/_slapdtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@

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
Expand All @@ -69,7 +75,7 @@ def skip_unless_ci(reason, feature=None):
"""
if not os.environ.get('CI', False):
return unittest.skip(reason)
elif feature in os.environ.get('CI_DISABLED', '').split(':'):
elif feature in CI_DISABLED:
return unittest.skip(reason)
else:
# Don't skip on Travis
Expand All @@ -95,6 +101,14 @@ def requires_sasl():
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 combined_logger(
log_name,
log_level=logging.WARN,
Expand Down Expand Up @@ -149,8 +163,6 @@ class SlapdObject(object):
root_dn = 'cn=%s,%s' % (root_cn, suffix)
root_pw = 'password'
slapd_loglevel = 'stats stats2'
# use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools
cli_sasl_external = True
local_host = '127.0.0.1'
testrunsubdirs = (
'schema',
Expand Down Expand Up @@ -192,8 +204,17 @@ def __init__(self):
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)
ldapi_path = os.path.join(self.testrundir, 'ldapi')
self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path)
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 = True
else:
self.ldapi_uri = None
self.default_ldap_uri = self.ldap_uri
# Use simple bind via LDAP uri
self.cli_sasl_external = False
# TLS certs
self.cafile = os.path.join(HERE, 'certs/ca.pem')
self.servercert = os.path.join(HERE, 'certs/server.pem')
Expand Down Expand Up @@ -331,11 +352,14 @@ 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', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)),
'-h', '%s' % ' '.join(urls),
]
if self._log.isEnabledFor(logging.DEBUG):
slapd_args.extend(['-d', '-1'])
Expand All @@ -346,18 +370,21 @@ def _start_slapd(self):
# 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
while 1: # pragma: no cover
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.ldapi_uri)
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):
"""
Expand Down Expand Up @@ -435,9 +462,11 @@ def _cli_auth_args(self):
# 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 or self.ldapi_uri,
'-H', ldap_uri,
] + self._cli_auth_args() + (extra_args or [])
self._log.debug('Run command: %r', ' '.join(args))
proc = subprocess.Popen(
Expand Down
5 changes: 3 additions & 2 deletions Tests/t_ldap_sasl.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

from ldap.ldapobject import SimpleLDAPObject
import ldap.sasl
from slapdtest import SlapdTestCase, requires_sasl, requires_tls
from slapdtest import SlapdTestCase
from slapdtest import requires_ldapi, requires_sasl, requires_tls


LDIF = """
Expand Down Expand Up @@ -60,7 +61,7 @@ def setUpClass(cls):
)
cls.server.ldapadd(ldif)

@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), "needs Unix socket")
@requires_ldapi()
def test_external_ldapi(self):
# EXTERNAL authentication with LDAPI (AF_UNIX)
ldap_conn = self.ldap_object_class(self.server.ldapi_uri)
Expand Down
3 changes: 2 additions & 1 deletion Tests/t_ldap_schema_subentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ldap.ldapobject import SimpleLDAPObject
import ldap.schema
from ldap.schema.models import ObjectClass
from slapdtest import SlapdTestCase
from slapdtest import SlapdTestCase, requires_ldapi

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

Expand Down Expand Up @@ -88,6 +88,7 @@ def test_urlfetch_ldap(self):
dn, schema = ldap.schema.urlfetch(self.server.ldap_uri)
self.assertSlapdSchema(dn, schema)

@requires_ldapi()
def test_urlfetch_ldapi(self):
dn, schema = ldap.schema.urlfetch(self.server.ldapi_uri)
self.assertSlapdSchema(dn, schema)
Expand Down
14 changes: 8 additions & 6 deletions Tests/t_ldapobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import unittest
import warnings
import pickle
import warnings
from slapdtest import SlapdTestCase, requires_sasl, requires_tls
from slapdtest import SlapdTestCase
from slapdtest import requires_ldapi, requires_sasl, requires_tls

# Switch off processing .ldaprc or ldap.conf before importing _ldap
os.environ['LDAPNOINIT'] = '1'
Expand Down Expand Up @@ -303,6 +303,7 @@ def test005_invalid_credentials(self):
self.fail("expected INVALID_CREDENTIALS, got %r" % r)

@requires_sasl()
@requires_ldapi()
def test006_sasl_extenal_bind_s(self):
l = self.ldap_object_class(self.server.ldapi_uri)
l.sasl_external_bind_s()
Expand Down Expand Up @@ -441,6 +442,7 @@ class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
ldap_object_class = ReconnectLDAPObject

@requires_sasl()
@requires_ldapi()
def test101_reconnect_sasl_external(self):
l = self.ldap_object_class(self.server.ldapi_uri)
l.sasl_external_bind_s()
Expand All @@ -450,15 +452,15 @@ def test101_reconnect_sasl_external(self):
self.assertEqual(l.whoami_s(), authz_id)

def test102_reconnect_simple_bind(self):
l = self.ldap_object_class(self.server.ldapi_uri)
l = self.ldap_object_class(self.server.ldap_uri)
bind_dn = 'cn=user1,'+self.server.suffix
l.simple_bind_s(bind_dn, 'user1_pw')
self.assertEqual(l.whoami_s(), 'dn:'+bind_dn)
self.server.restart()
self.assertEqual(l.whoami_s(), 'dn:'+bind_dn)

def test103_reconnect_get_state(self):
l1 = self.ldap_object_class(self.server.ldapi_uri)
l1 = self.ldap_object_class(self.server.ldap_uri)
bind_dn = 'cn=user1,'+self.server.suffix
l1.simple_bind_s(bind_dn, 'user1_pw')
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
Expand All @@ -477,15 +479,15 @@ def test103_reconnect_get_state(self):
str('_start_tls'): 0,
str('_trace_level'): 0,
str('_trace_stack_limit'): 5,
str('_uri'): self.server.ldapi_uri,
str('_uri'): self.server.ldap_uri,
str('bytes_mode'): l1.bytes_mode,
str('bytes_mode_hardfail'): l1.bytes_mode_hardfail,
str('timeout'): -1,
},
)

def test104_reconnect_restore(self):
l1 = self.ldap_object_class(self.server.ldapi_uri)
l1 = self.ldap_object_class(self.server.ldap_uri)
bind_dn = 'cn=user1,'+self.server.suffix
l1.simple_bind_s(bind_dn, 'user1_pw')
self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn)
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ basepython = python2
deps = {[testenv]deps}
passenv = {[testenv]passenv}
setenv =
CI_DISABLED=TLS:SASL
# rebuild without SASL and TLS
CI_DISABLED=LDAPI:SASL:TLS
# rebuild without SASL and TLS, run without LDAPI
commands = {envpython} \
-m coverage run --parallel setup.py \
clean --all \
Expand Down

0 comments on commit aa0a07b

Please sign in to comment.