URL: https://github.com/SSSD/sssd/pull/391
Author: lslebodn
 Title: #391: Use dbus-daemon in cwrap enviroment for test
Action: synchronized

To pull the PR as Git branch:
git remote add ghsssd https://github.com/SSSD/sssd
git fetch ghsssd pull/391/head:pr391
git checkout pr391
From 041b25fef6e027a8aeecf59fb213f943f9d9458c Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lsleb...@redhat.com>
Date: Wed, 20 Sep 2017 15:57:26 +0200
Subject: [PATCH 1/5] intg: Build with optimisations and debug symbols

We override CFLAGS for macro KCM_PEER_UID. Such change also remove
standard CFLAGS (-O2 -g) and therefore it was not possible to debug
processes in gdb unless environment variable CFLAGS was set.
But we should test optimized code by default and let developers
override default with environment variable CFLAGS and not vice versa.
---
 Makefile.am | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index d02f8fe1a..25e996d2d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3607,7 +3607,7 @@ intgcheck-prepare:
 	    --without-semanage \
 	    --with-session-recording-shell=/bin/false \
 	    $(INTGCHECK_CONFIGURE_FLAGS) \
-	    CFLAGS="$$CFLAGS -DKCM_PEER_UID=$$(id -u)"; \
+	    CFLAGS="-O2 -g $$CFLAGS -DKCM_PEER_UID=$$(id -u)"; \
 	$(MAKE) $(AM_MAKEFLAGS) ; \
 	: Force single-thread install to workaround concurrency issues; \
 	$(MAKE) $(AM_MAKEFLAGS) -j1 install; \

From 80d1376c54ea6071eb32de4e16d0729010304d3d Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lsleb...@redhat.com>
Date: Sat, 18 Mar 2017 15:47:43 +0100
Subject: [PATCH 2/5] intg: Do not prefer builddir in PATH

Binary files in builddir are shell wrapper for libtool
Therefore we should prefer files which are installed in $prefix
---
 src/tests/intg/Makefile.am | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 209e5a0c7..9c4079d97 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -85,7 +85,7 @@ intgcheck-installed: config.py passwd group
 	unset HOME; \
 	PATH="$$(dirname -- $(SLAPD)):$$PATH" \
 	PATH="$(DESTDIR)$(sbindir):$(DESTDIR)$(bindir):$$PATH" \
-	PATH="$(abs_builddir):$(abs_srcdir):$$PATH" \
+	PATH="$$PATH:$(abs_builddir):$(abs_srcdir)" \
 	PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \
 	LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \
 	NON_WRAPPED_UID=$$(id -u) \

From 2ba5ae78a2ee94e0e99f2b0e6cda37fca4b781e5 Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lsleb...@redhat.com>
Date: Wed, 20 Sep 2017 15:40:07 +0200
Subject: [PATCH 3/5] intg: Install configuration for dbus daemon

Resolves:
https://pagure.io/SSSD/sssd/issue/2823
---
 src/external/intgcheck.m4                     |  2 +
 src/tests/intg/Makefile.am                    | 20 +++++++
 src/tests/intg/data/cwrap-dbus-system.conf.in | 83 +++++++++++++++++++++++++++
 3 files changed, 105 insertions(+)
 create mode 100644 src/tests/intg/data/cwrap-dbus-system.conf.in

diff --git a/src/external/intgcheck.m4 b/src/external/intgcheck.m4
index ac68b85dd..60a7bf306 100644
--- a/src/external/intgcheck.m4
+++ b/src/external/intgcheck.m4
@@ -31,3 +31,5 @@ AC_DEFUN([SSS_ENABLE_INTGCHECK_REQS], [
         SSS_INTGCHECK_REQ([HAVE_PY2MOD_LDAP], [pyldb])
     fi
 ])
+
+AM_CONDITIONAL([INTG_BUILD], [test x"$enable_intgcheck_reqs" = xyes])
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 9c4079d97..23737624d 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -37,6 +37,25 @@ dist_noinst_DATA = \
     test_pysss_nss_idmap.py \
     $(NULL)
 
+EXTRA_DIST = data/cwrap-dbus-system.conf.in
+
+dbussysconfdir = $(sysconfdir)/dbus-1
+dbusservicedir = $(datadir)/dbus-1/system-services
+
+if INTG_BUILD
+dist_dbussysconf_DATA = cwrap-dbus-system.conf
+
+install-data-hook:
+	$(MKDIR_P) $(DESTDIR)$(runstatedir)/dbus
+	$(MKDIR_P) $(DESTDIR)$(sysconfdir)/session.d
+
+endif
+
+cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile
+	$(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \
+           -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \
+       $< > $@
+
 config.py: config.py.m4
 	m4 -D "prefix=\`$(prefix)'" \
 	   -D "sysconfdir=\`$(sysconfdir)'" \
@@ -70,6 +89,7 @@ CLEANFILES=config.py config.pyc passwd group
 
 clean-local:
 	rm -Rf root
+	rm -f $(builddir)/cwrap-dbus-system.conf
 
 intgcheck-installed: config.py passwd group
 	pipepath="$(DESTDIR)$(pipepath)"; \
diff --git a/src/tests/intg/data/cwrap-dbus-system.conf.in b/src/tests/intg/data/cwrap-dbus-system.conf.in
new file mode 100644
index 000000000..7369054e7
--- /dev/null
+++ b/src/tests/intg/data/cwrap-dbus-system.conf.in
@@ -0,0 +1,83 @@
+<!-- This configuration file controls the systemwide message bus.
+     Add a system-local.conf and edit that rather than changing this
+     file directly. -->
+
+<!-- Note that there are any number of ways you can hose yourself
+     security-wise by screwing up this file; in particular, you
+     probably don't want to listen on any more addresses, add any more
+     auth mechanisms, run as a different user, etc. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
+<busconfig>
+
+  <!-- Our well-known bus type, do not change this -->
+  <type>system</type>
+
+  <!-- If we fork, keep the user's original umask to avoid affecting
+       the behavior of child processes. -->
+  <keep_umask/>
+
+
+  <!-- Fork into daemon mode -->
+  <fork/>
+
+  <!-- We use system service launching using a helper -->
+  <standard_system_servicedirs/>
+  <servicedir>@dbusservicedir@</servicedir>
+
+
+  <!-- Write a pid file -->
+  <pidfile>@runstatedir@/dbus/messagebus.pid</pidfile>
+
+  <!-- On Unix systems, the most secure authentication mechanism is
+  EXTERNAL, which uses credential-passing over Unix sockets.
+
+  This authentication mechanism is not available on Windows,
+  is not suitable for use with the tcp: or nonce-tcp: transports,
+  and will not work on obscure flavours of Unix that do not have
+  a supported credentials-passing mechanism. On those platforms/transports,
+  comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. -->
+  <auth>EXTERNAL</auth>
+
+  <!-- Only listen on a local socket. (abstract=/path/to/socket
+       means use abstract namespace, don't really create filesystem
+       file; only Linux supports this. Use path=/whatever on other
+       systems.) -->
+  <listen>unix:path=@runstatedir@/dbus/system_bus_socket</listen>
+  <policy context="default">
+    <!-- Allow everything to be sent -->
+    <allow send_destination="*" eavesdrop="true"/>
+    <!-- Allow everything to be received -->
+    <allow eavesdrop="true"/>
+    <!-- Allow anyone to own anything -->
+    <allow own="*"/>
+  </policy>
+
+  <!-- Config files are placed here that among other things, punch
+       holes in the above policy for specific services. -->
+  <includedir>system.d</includedir>
+
+  <!--
+  <includedir>/etc/dbus-1/system.d</includedir>
+  -->
+
+  <!-- This is included last so local configuration can override what's
+       in this standard file -->
+  <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>
+
+  <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
+
+  <!-- For the session bus, override the default relatively-low limits
+       with essentially infinite limits, since the bus is just running
+       as the user anyway, using up bus resources is not something we need
+       to worry about. In some cases, we do set the limits lower than
+       "all available memory" if exceeding the limit is almost certainly a bug,
+       having the bus enforce a limit is nicer than a huge memory leak. But the
+       intent is that these limits should never be hit. -->
+
+  <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
+  <!-- We do not override max_message_unix_fds here since the in-kernel
+       limit is also relatively low -->
+
+</busconfig>

From 23e017f3100c5b3e73127f6a808200b49d602b73 Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lsleb...@redhat.com>
Date: Wed, 20 Sep 2017 15:49:28 +0200
Subject: [PATCH 4/5] intg: Install wrapper for getsockopt

The dbus-daemon detect remote user from unix socket using
getsockopt + SO_PEERCRED. Neither fakeroot nor cwrap can fake it.
And it would take some time till patched version would get to downstream
distributions.

Resolves:
https://pagure.io/SSSD/sssd/issue/2823
---
 src/tests/intg/Makefile.am          | 19 +++++++++++-
 src/tests/intg/getsockopt_wrapper.c | 59 +++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 src/tests/intg/getsockopt_wrapper.c

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 23737624d..1edf52f9c 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -43,6 +43,19 @@ dbussysconfdir = $(sysconfdir)/dbus-1
 dbusservicedir = $(datadir)/dbus-1/system-services
 
 if INTG_BUILD
+lib_LTLIBRARIES = getsockopt_wrapper.la
+
+getsockopt_wrapper_la_SOURCES = \
+    getsockopt_wrapper.c
+getsockopt_wrapper_la_CFLAGS = \
+    $(AM_CFLAGS)
+getsockopt_wrapper_la_LIBADD = \
+    $(LIBADD_DL) \
+    $(NULL)
+getsockopt_wrapper_la_LDFLAGS = \
+    -avoid-version \
+    -module
+
 dist_dbussysconf_DATA = cwrap-dbus-system.conf
 
 install-data-hook:
@@ -109,12 +122,16 @@ intgcheck-installed: config.py passwd group
 	PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \
 	LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \
 	NON_WRAPPED_UID=$$(id -u) \
-	LD_PRELOAD="$$nss_wrapper $$uid_wrapper" \
+	LD_PRELOAD="$(libdir)/getsockopt_wrapper.so:$$nss_wrapper:$$uid_wrapper" \
 	NSS_WRAPPER_PASSWD="$(abs_builddir)/passwd" \
 	NSS_WRAPPER_GROUP="$(abs_builddir)/group" \
 	NSS_WRAPPER_MODULE_SO_PATH="$(DESTDIR)$(nsslibdir)/libnss_sss.so.2" \
 	NSS_WRAPPER_MODULE_FN_PREFIX="sss" \
 	UID_WRAPPER=1 \
 	UID_WRAPPER_ROOT=1 \
+	DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \
+	DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \
+	DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \
+	DBUS_SYSTEM_BUS_DEFAULT_ADDRESS="$$DBUS_SYSTEM_BUS_ADDRESS" \
 	    fakeroot $(PYTHON2) $(PYTEST) -v --tb=native $(INTGCHECK_PYTEST_ARGS) .
 	rm -f $(DESTDIR)$(logpath)/*
diff --git a/src/tests/intg/getsockopt_wrapper.c b/src/tests/intg/getsockopt_wrapper.c
new file mode 100644
index 000000000..77c832329
--- /dev/null
+++ b/src/tests/intg/getsockopt_wrapper.c
@@ -0,0 +1,59 @@
+/* gcc -Wall -fPIC -shared -o getsockopt_wrapper.so getsockopt_wrapper.c -ldl */
+
+/* for RTLD_NEXT */
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <dlfcn.h>
+
+static bool is_dbus_socket(int fd)
+{
+    int ret;
+    struct sockaddr_storage addr = { 0 };
+    socklen_t addrlen = sizeof(addr);
+    struct sockaddr_un *unix_socket;
+
+    ret = getsockname(fd, (struct sockaddr *)&addr, &addrlen);
+    if (ret != 0) return false;
+
+    if (addr.ss_family != AF_UNIX) return false;
+
+    unix_socket = (struct sockaddr_un *)&addr;
+
+    return NULL != strstr(unix_socket->sun_path, "system_bus_socket");
+}
+
+typedef typeof(getsockopt) getsockopt_fn_t;
+
+static getsockopt_fn_t *orig_getsockopt = NULL;
+
+int getsockopt(int sockfd, int level, int optname,
+               void *optval, socklen_t *optlen)
+{
+    int ret;
+#ifdef __OpenBSD__
+    struct sockpeercred *cr;
+#else
+    struct ucred *cr;
+#endif
+
+    if (orig_getsockopt == NULL) {
+        orig_getsockopt = (getsockopt_fn_t *)dlsym(RTLD_NEXT, "getsockopt");
+    }
+
+    ret = orig_getsockopt(sockfd, level, optname, optval, optlen);
+
+    if (ret == 0 && level == SOL_SOCKET && optname == SO_PEERCRED
+            && *optlen == sizeof(*cr)) {
+        cr = optval;
+        if (cr->uid != 0 && is_dbus_socket(sockfd)) {
+            cr->uid = 0;
+        }
+    }
+
+    return ret;
+}

From d8354c07ec9a1d81f2b93a4c514317b37129a08d Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lsleb...@redhat.com>
Date: Mon, 18 Sep 2017 23:13:58 +0200
Subject: [PATCH 5/5] intg: Add sample infopipe test in cwrap env

Resolves:
https://pagure.io/SSSD/sssd/issue/2823
---
 contrib/ci/deps.sh              |   2 +
 src/tests/intg/Makefile.am      |   1 +
 src/tests/intg/test_infopipe.py | 532 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 535 insertions(+)
 create mode 100644 src/tests/intg/test_infopipe.py

diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh
index 4467e117c..89e6aae0e 100644
--- a/contrib/ci/deps.sh
+++ b/contrib/ci/deps.sh
@@ -49,6 +49,7 @@ if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
         curl-devel
         krb5-server
         krb5-workstation
+        dbus-python
     )
     _DEPS_LIST_SPEC=`
         sed -e 's/@PACKAGE_VERSION@/0/g' \
@@ -128,6 +129,7 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
         krb5-admin-server
         krb5-user
         uuid-dev
+        python-dbus
     )
     DEPS_INTGCHECK_SATISFIED=true
 fi
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 1edf52f9c..9c5338261 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -35,6 +35,7 @@ dist_noinst_DATA = \
     data/ad_data.ldif \
     data/ad_schema.ldif \
     test_pysss_nss_idmap.py \
+    test_infopipe.py \
     $(NULL)
 
 EXTRA_DIST = data/cwrap-dbus-system.conf.in
diff --git a/src/tests/intg/test_infopipe.py b/src/tests/intg/test_infopipe.py
new file mode 100644
index 000000000..9f09c3a9f
--- /dev/null
+++ b/src/tests/intg/test_infopipe.py
@@ -0,0 +1,532 @@
+#
+# Infopipe integration test
+#
+# Copyright (c) 2017 Red Hat, Inc.
+# Author: Lukas Slebodnik <lsleb...@redhat.com>
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import print_function
+
+import os
+import stat
+import pwd
+import signal
+import subprocess
+import errno
+import time
+import ldap
+import ldap.modlist
+import pytest
+import dbus
+
+import config
+import ds_openldap
+import ent
+import ldap_ent
+from util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+INTERACTIVE_TIMEOUT = 4
+
+
+class DbusDaemon(object):
+    def __init__(self):
+        self.pid = 0
+
+    def start(self):
+        """Start the SSSD process"""
+        assert self.pid == 0
+
+        dbus_config_path = config.SYSCONFDIR + "/dbus-1/cwrap-dbus-system.conf"
+        dbus_commands = [
+            ["dbus-daemon", "--config-file", dbus_config_path,
+             "--nosyslog", "--fork"],
+            ["dbus-daemon", "--config-file", dbus_config_path, "--fork"],
+        ]
+        dbus_started = False
+        for dbus_command in dbus_commands:
+            try:
+                if subprocess.call(dbus_command) == 0:
+                    dbus_started = True
+                    break
+                else:
+                    print("start failed for %s" % " ".join(dbus_command))
+            except OSError as ex:
+                if ex.errno == errno.ENOENT:
+                    print("%s does not exist" % (dbus_command[0]))
+                    pass
+
+        if not dbus_started:
+            raise Exception("dbus-daemon start failed")
+        dbus_pid_path = config.RUNSTATEDIR + "/dbus/messagebus.pid"
+        # wait 10 seconds for pidfile
+        wait_time = 10
+        for _ in range(wait_time * 10):
+            if os.path.isfile(dbus_pid_path):
+                break
+            time.sleep(.1)
+
+        assert os.path.isfile(dbus_pid_path)
+        with open(dbus_pid_path, "r") as pid_file:
+            self.pid = int(pid_file.read())
+
+    def stop(self):
+        """Stop the SSSD process and remove its state"""
+
+        # stop process only if running
+        if self.pid != 0:
+            try:
+                os.kill(self.pid, signal.SIGTERM)
+                while True:
+                    try:
+                        os.kill(self.pid, signal.SIGCONT)
+                    except:
+                        break
+                    time.sleep(.1)
+            except:
+                pass
+
+        # clean pid so we can start service one more time
+        self.pid = 0
+
+        # dbus-daemon 1.2.24 does not clean pid file after itself
+        try:
+            os.unlink(config.RUNSTATEDIR + "/dbus/messagebus.pid")
+        except OSError as ex:
+            if ex.errno != errno.ENOENT:
+                raise
+
+
+@pytest.fixture(scope="module")
+def dbus_system_bus(request):
+    dbus_daemon = DbusDaemon()
+    dbus_daemon.start()
+
+    def cleanup_dbus_process():
+        dbus_daemon.stop()
+    request.addfinalizer(cleanup_dbus_process)
+
+    return dbus.SystemBus()
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+    """LDAP server instance fixture"""
+    ds_inst = ds_openldap.DSOpenLDAP(
+        config.PREFIX, 10389, LDAP_BASE_DN,
+        "cn=admin", "Secret123"
+    )
+
+    try:
+        ds_inst.setup()
+    except:
+        ds_inst.teardown()
+        raise
+    request.addfinalizer(ds_inst.teardown)
+
+    return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+    """LDAP server connection fixture"""
+    ldap_conn = ds_inst.bind()
+    ldap_conn.ds_inst = ds_inst
+    request.addfinalizer(ldap_conn.unbind_s)
+    return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+    """Add LDAP entries from ent_list"""
+    if ent_list is not None:
+        for entry in ent_list:
+            ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+    """Remove LDAP entries added by create_ldap_entries"""
+    if ent_list is None:
+        for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+            for entry in ldap_conn.search_s("ou=" + ou + "," +
+                                            ldap_conn.ds_inst.base_dn,
+                                            ldap.SCOPE_ONELEVEL,
+                                            attrlist=[]):
+                ldap_conn.delete_s(entry[0])
+    else:
+        for entry in ent_list:
+            ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+    """Add teardown for removing all user/group LDAP entries"""
+    request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+    """Add LDAP entries and add teardown for removing them"""
+    create_ldap_entries(ldap_conn, ent_list)
+    create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+    """Format a basic SSSD configuration"""
+    schema_conf = "ldap_schema         = " + schema + "\n"
+    if schema == SCHEMA_RFC2307_BIS:
+        schema_conf += "ldap_group_object_class = groupOfNames\n"
+
+    valgrind_cmd = "valgrind --log-file=%s/valgrind_ifp.log" % config.LOG_PATH
+    ifp_command = "%s %s/sssd/sssd_ifp " % (valgrind_cmd, config.LIBEXEC_PATH)
+    return unindent("""\
+        [sssd]
+        debug_level         = 0xffff
+        domains             = LDAP
+        services            = nss, ifp
+        enable_files_domain = false
+
+        [nss]
+        memcache_timeout    = 0
+
+        [ifp]
+        # it need to be executed with valgrind because there is a problem
+        # problem with "ifp" + client regristration in monitor
+        # There is not such problem in 1st test. Just in following tests.
+        command = {ifp_command} --uid 0 --gid 0 --debug-to-files
+
+        [domain/LDAP]
+        {schema_conf}
+        id_provider         = ldap
+        ldap_uri            = {ldap_conn.ds_inst.ldap_url}
+        ldap_search_base    = {ldap_conn.ds_inst.base_dn}
+    """).format(**locals())
+
+
+def format_interactive_conf(ldap_conn, schema):
+    """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+    return \
+        format_basic_conf(ldap_conn, schema) + \
+        unindent("""
+            [nss]
+            memcache_timeout                    = 0
+            entry_negative_timeout              = 0
+
+            [domain/LDAP]
+            ldap_purge_cache_timeout            = 1
+            entry_cache_timeout                 = {0}
+        """).format(INTERACTIVE_TIMEOUT)
+
+
+def create_conf_file(contents):
+    """Create sssd.conf with specified contents"""
+    with open(config.CONF_PATH, "w") as conf:
+        conf.write(contents)
+    os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+    """Remove sssd.conf, if it exists"""
+    if os.path.lexists(config.CONF_PATH):
+        os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+    """Add teardown for removing sssd.conf"""
+    request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+    """
+    Create sssd.conf with specified contents and add teardown for removing it
+    """
+    create_conf_file(contents)
+    create_conf_cleanup(request)
+
+
+def create_sssd_process():
+    """Start the SSSD process"""
+    if subprocess.call(["sssd", "-D", "-f"]) != 0:
+        raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+    """Stop the SSSD process and remove its state"""
+    try:
+        with open(config.PIDFILE_PATH, "r") as pid_file:
+            pid = int(pid_file.read())
+        os.kill(pid, signal.SIGTERM)
+        while True:
+            try:
+                os.kill(pid, signal.SIGCONT)
+            except:
+                break
+            time.sleep(1)
+    except:
+        pass
+    for path in os.listdir(config.DB_PATH):
+        os.unlink(config.DB_PATH + "/" + path)
+    for path in os.listdir(config.MCACHE_PATH):
+        os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+    """Add teardown for stopping SSSD and removing its state"""
+    request.addfinalizer(cleanup_sssd_process)
+
+
+def create_sssd_fixture(request):
+    """Start SSSD and add teardown for stopping it and removing its state"""
+    create_sssd_process()
+    create_sssd_cleanup(request)
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+    ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+    ent_list.add_user("user1", 1001, 2001)
+    ent_list.add_user("user2", 1002, 2002)
+    ent_list.add_user("user3", 1003, 2003)
+
+    ent_list.add_group("group1", 2001)
+    ent_list.add_group("group2", 2002)
+    ent_list.add_group("group3", 2003)
+
+    ent_list.add_group("empty_group", 2010)
+
+    ent_list.add_group("single_user_group", 2011, ["user1"])
+    ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+
+    create_ldap_fixture(request, ldap_conn, ent_list)
+
+    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+    return None
+
+
+@pytest.fixture
+def simple_rfc2307(request, ldap_conn):
+    ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+    ent_list.add_user('usr\\\\001', 181818, 181818)
+    ent_list.add_group("group1", 181818)
+    create_ldap_fixture(request, ldap_conn, ent_list)
+    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+    return None
+
+
+def test_ping_raw(dbus_system_bus, ldap_conn, simple_rfc2307):
+    # test with disabled introspection
+    sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+                                          '/org/freedesktop/sssd/infopipe',
+                                          introspect=False)
+    sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+    # test missing parameter
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.Ping()
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+    assert ex.get_dbus_message() == 'Argument 0 is specified to be of type ' \
+                                    '"string", but is actually of type ' \
+                                    '"invalid"\n'
+
+    # test wrong parameter type
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.Ping(1)
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+    assert ex.get_dbus_message() == 'Argument 0 is specified to be of type ' \
+                                    '"string", but is actually of type ' \
+                                    '"int32"\n'
+
+    # test wrong parameter value
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.Ping('test')
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+    assert ex.get_dbus_message() == 'Ping() only accepts "ping" as a param\n'
+
+    # positive test
+    ret = sssd_interface.Ping('ping')
+    assert ret == "PONG"
+
+    # test case insensitive input
+    ret = sssd_interface.Ping('PinG')
+    assert ret == "PONG"
+
+    ret = sssd_interface.Ping('PING')
+    assert ret == "PONG"
+
+def test_ping_introspection(dbus_system_bus, ldap_conn, simple_rfc2307):
+    sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+                                          '/org/freedesktop/sssd/infopipe')
+    sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+    # test missing parameter
+    with pytest.raises(TypeError) as exc_info:
+        sssd_interface.Ping()
+    assert exc_info.errisinstance(TypeError)
+
+    ex = exc_info.value
+    assert str(ex) == 'More items found in D-Bus signature than in Python ' \
+                      'arguments'
+
+    # test wrong parameter type
+    with pytest.raises(TypeError) as exc_info:
+        sssd_interface.Ping(1)
+    assert exc_info.errisinstance(TypeError)
+
+    ex = exc_info.value
+    assert str(ex) == 'Expected a string or unicode object'
+
+    # test wrong parameter value
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.Ping('test')
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+    assert ex.get_dbus_message() == 'Ping() only accepts "ping" as a param\n'
+
+    # positive test
+    ret = sssd_interface.Ping('ping')
+    assert ret == "PONG"
+
+    # test case insensitive input
+    ret = sssd_interface.Ping('PinG')
+    assert ret == "PONG"
+
+    ret = sssd_interface.Ping('PING')
+    assert ret == "PONG"
+
+
+def test_special_characters(dbus_system_bus, ldap_conn, simple_rfc2307):
+    sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+                                          '/org/freedesktop/sssd/infopipe')
+    sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+    attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory',
+                  'loginShell']
+    expected = dict(name='usr\\001', uidNumber='181818', gidNumber='181818',
+                    gecos='181818', homeDirectory='/home/usr\\\\001',
+                    loginShell='/bin/bash')
+
+    user_attrs = sssd_interface.GetUserAttr('usr\\001', attributes)
+    assert user_attrs.signature == 'sv'
+    assert user_attrs.variant_level == 0
+
+    assert len(attributes) == len(user_attrs)
+    assert sorted(attributes) == sorted(user_attrs.keys())
+
+    # check values of attributes
+    for attr in user_attrs:
+        assert user_attrs[attr].signature == 's'
+        assert user_attrs[attr].variant_level == 1
+        assert user_attrs[attr][0] == expected[attr]
+
+
+def test_get_user_attr(dbus_system_bus, ldap_conn, sanity_rfc2307):
+    sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+                                          '/org/freedesktop/sssd/infopipe')
+    sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+    # negative test
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.GetUserAttr('non_existent_user', ['name'])
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.Failed'
+    assert ex.get_dbus_message() == 'No such user\n'
+
+    # test 0 attributes
+    user_attrs = sssd_interface.GetUserAttr('user1', [])
+
+    assert user_attrs.signature == 'sv'
+    assert user_attrs.variant_level == 0
+
+    assert len(user_attrs) == 0
+
+    # positive test
+    attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory',
+                  'loginShell']
+    expected = dict(name='user1', uidNumber='1001', gidNumber='2001',
+                    gecos='1001', homeDirectory='/home/user1',
+                    loginShell='/bin/bash')
+    user_attrs = sssd_interface.GetUserAttr('user1', attributes)
+
+    assert user_attrs.signature == 'sv'
+    assert user_attrs.variant_level == 0
+
+    assert len(attributes) == len(user_attrs)
+    assert sorted(attributes) == sorted(user_attrs.keys())
+
+    # check values of attributes
+    for attr in user_attrs:
+        assert user_attrs[attr].signature == 's'
+        assert user_attrs[attr].variant_level == 1
+        assert user_attrs[attr][0] == expected[attr]
+
+
+def test_get_user_groups(dbus_system_bus, ldap_conn, sanity_rfc2307):
+    sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+                                          '/org/freedesktop/sssd/infopipe')
+    sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+    # negative test
+    with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+        sssd_interface.GetUserGroups('non_existent_user')
+    assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+    ex = exc_info.value
+    assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.Failed'
+    assert ex.get_dbus_message() == 'No such user\n'
+
+    # the same test via nss responder
+    with pytest.raises(KeyError):
+        pwd.getpwnam("non_existent_user")
+
+    # 0 groups
+    res = sssd_interface.GetUserGroups('user3')
+    assert res.signature == 's'
+    assert res.variant_level == 0
+
+    assert len(res) == 0
+
+    # single group
+    res = sssd_interface.GetUserGroups('user2')
+    assert res.signature == 's'
+    assert res.variant_level == 0
+
+    assert len(res) == 1
+    assert res[0] == 'two_user_group'
+
+    # more groups
+    res = sssd_interface.GetUserGroups('user1')
+    assert res.signature == 's'
+    assert res.variant_level == 0
+
+    assert len(res) == 2
+    assert sorted(res) == ['single_user_group', 'two_user_group']
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org

Reply via email to