summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2009-09-14 13:03:57 +0200
committerStephen Gallagher <sgallagh@redhat.com>2009-09-21 10:35:08 -0400
commit9570ca098cd0e92d1eb6aabc00fb8cac9fddd442 (patch)
tree38c745cd87c48aac9c195314782edd954c9e8287 /server
parent8f035b043dbd4eb631b64c4d7dcdb35272e35142 (diff)
downloadsssd-9570ca098cd0e92d1eb6aabc00fb8cac9fddd442.tar.gz
sssd-9570ca098cd0e92d1eb6aabc00fb8cac9fddd442.tar.xz
sssd-9570ca098cd0e92d1eb6aabc00fb8cac9fddd442.zip
Provide python bindings for sysdb
Implement a set of python bindings for the sysdb with feature set similar to what is available in the tools. The primary consumers would be applications like system-config-users. Resolves: Ticket #102
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am34
-rw-r--r--server/conf_macros.m417
-rw-r--r--server/configure.ac9
-rw-r--r--server/external/python.m449
-rw-r--r--server/python/pysss.c1024
-rw-r--r--server/tests/python-test.py391
-rw-r--r--server/tools/tools_util.h2
-rw-r--r--server/util/util.h8
8 files changed, 1534 insertions, 0 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 6036110bf..8ea773b06 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -88,6 +88,11 @@ libsss_crypt_la_SOURCES = \
libsss_crypt_la_CPPFLAGS = \
$(NSS_CFLAGS)
+if BUILD_PYTHON_BINDINGS
+pyexec_LTLIBRARIES = \
+ pysss.la
+endif
+
###############################
# Global compilation settings #
###############################
@@ -201,6 +206,18 @@ SSSD_LIBS = \
$(NSS_LIBS) \
libsss_crypt.la
+PYTHON_BINDINGS_LIBS = \
+ $(TALLOC_LIBS) \
+ $(TDB_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(LDB_LIBS) \
+ $(DBUS_LIBS) \
+ $(REPLACE_LIBS) \
+ $(PCRE_LIBS) \
+ $(NSS_LIBS) \
+ libsss_crypt.la
+
dist_noinst_HEADERS = \
monitor/monitor.h \
util/btreemap.h \
@@ -443,6 +460,7 @@ memberof_la_SOURCES = \
ldb_modules/memberof.c
memberof_la_CFLAGS = \
$(AM_CFLAGS)
+memberof_la_LIBADD = $(LDB_LIBS)
memberof_la_LDFLAGS = \
-avoid-version \
-module
@@ -456,6 +474,22 @@ sssd_krb5_locator_plugin_la_LDFLAGS = \
-avoid-version \
-module
+if BUILD_PYTHON_BINDINGS
+pysss_la_SOURCES = \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ) \
+ python/pysss.c
+pysss_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(PYTHON_CFLAGS)
+pysss_la_LIBADD = \
+ $(PYTHON_BINDINGS_LIBS) \
+ $(PYTHON_LIBS)
+pysss_la_LDFLAGS = \
+ -avoid-version \
+ -module
+endif
+
############
# MANPAGES #
############
diff --git a/server/conf_macros.m4 b/server/conf_macros.m4
index aa2d578f7..9ab2003e9 100644
--- a/server/conf_macros.m4
+++ b/server/conf_macros.m4
@@ -137,3 +137,20 @@ AC_DEFUN([WITH_KRB5_PLUGIN_PATH],
fi
AC_SUBST(krb5pluginpath)
])
+
+AC_DEFUN([WITH_PYTHON_BINDINGS],
+ [ AC_ARG_WITH([python-bindings],
+ [AC_HELP_STRING([--with-python-bindings],
+ [Whether to build python bindings [yes]]
+ )
+ ],
+ [],
+ with_python_bindings=yes
+ )
+ if test x"$with_python_bindings" == xyes; then
+ HAVE_PYTHON_BINDINGS=1
+ AC_SUBST(HAVE_PYTHON_BINDINGS)
+ fi
+ AM_CONDITIONAL([BUILD_PYTHON_BINDINGS], [test x"$with_python_bindings" = xyes])
+ ])
+
diff --git a/server/configure.ac b/server/configure.ac
index 3320507ad..475bdca6a 100644
--- a/server/configure.ac
+++ b/server/configure.ac
@@ -48,6 +48,7 @@ WITH_SHADOW_UTILS_PATH
WITH_MANPAGES
WITH_XML_CATALOG
WITH_KRB5_PLUGIN_PATH
+WITH_PYTHON_BINDINGS
m4_include([external/pkg.m4])
m4_include([external/libpopt.m4])
@@ -62,6 +63,7 @@ m4_include([external/krb5.m4])
m4_include([external/libcares.m4])
m4_include([external/docbook.m4])
m4_include([external/sizes.m4])
+m4_include([external/python.m4])
m4_include([util/signal.m4])
PKG_CHECK_MODULES([DBUS],[dbus-1])
@@ -83,6 +85,13 @@ if test x$HAVE_MANPAGES != x; then
[Docbook XSL templates])
fi
+if test x$HAVE_PYTHON_BINDINGS != x; then
+ AM_PATH_PYTHON([2.4])
+ AM_CHECK_PYTHON_HEADERS([],
+ AC_MSG_ERROR([Could not find python headers]))
+ AM_PYTHON_CONFIG
+fi
+
AC_CHECK_HEADERS([sys/inotify.h])
PKG_CHECK_MODULES([CHECK], [check], [have_check=1], [have_check=])
diff --git a/server/external/python.m4 b/server/external/python.m4
new file mode 100644
index 000000000..37eeac24f
--- /dev/null
+++ b/server/external/python.m4
@@ -0,0 +1,49 @@
+dnl Check for python-config and substitute needed CFLAGS and LDFLAGS
+dnl Usage:
+dnl AM_PYTHON_CONFIG
+AC_DEFUN([AM_PYTHON_CONFIG],
+[ AC_SUBST(PYTHON_CFLAGS)
+ AC_SUBST(PYTHON_LIBS)
+
+ AC_PATH_PROG(PYTHON_CONFIG, python-config)
+ AC_MSG_CHECKING(for working python-config)
+ if test -x "$PYTHON_CONFIG"; then
+ PYTHON_CFLAGS="`$PYTHON_CONFIG --includes`"
+ PYTHON_CFLAGS=$PYTHON_CFLAGS" -fno-strict-aliasing"
+ PYTHON_LIBS="`$PYTHON_CONFIG --libs`"
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_ERROR([no. Please install python devel package])
+ fi
+])
+
+dnl Taken from GNOME sources
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+ AC_MSG_CHECKING(for headers required to compile python extensions)
+
+ dnl deduce PYTHON_INCLUDES
+ py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+ py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+ PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+ if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+ fi
+
+ AC_SUBST(PYTHON_INCLUDES)
+
+ dnl check if the headers exist:
+ save_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+ AC_TRY_CPP([#include <Python.h>],dnl
+ [AC_MSG_RESULT([found])
+ $1],dnl
+ [AC_MSG_RESULT([not found])
+ $2])
+ CPPFLAGS="$save_CPPFLAGS"
+])
+
+
diff --git a/server/python/pysss.c b/server/python/pysss.c
new file mode 100644
index 000000000..aab17186d
--- /dev/null
+++ b/server/python/pysss.c
@@ -0,0 +1,1024 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ This program 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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/>.
+*/
+
+#include <Python.h>
+#include <structmember.h>
+#include <talloc.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+#define TRANSACTION_WAIT(trs, retval) do { \
+ while (!trs->transaction_done) { \
+ tevent_loop_once(trs->self->ev); \
+ } \
+ retval = trs->error; \
+ if (retval) { \
+ PyErr_SetSssError(retval); \
+ goto fail; \
+ } \
+} while(0)
+
+/*
+ * function taken from samba sources tree as of Aug 20 2009,
+ * file source4/lib/ldb/pyldb.c
+ */
+static char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list,
+ const char *paramname)
+{
+ char **ret;
+ int i;
+
+ ret = talloc_array(NULL, char *, PyList_Size(list)+1);
+ for (i = 0; i < PyList_Size(list); i++) {
+ PyObject *item = PyList_GetItem(list, i);
+ if (!PyString_Check(item)) {
+ PyErr_Format(PyExc_TypeError, "%s should be strings", paramname);
+ return NULL;
+ }
+ ret[i] = talloc_strndup(ret, PyString_AsString(item),
+ PyString_Size(item));
+ }
+
+ ret[i] = NULL;
+ return ret;
+}
+
+/*
+ * The sss.local object
+ */
+typedef struct {
+ PyObject_HEAD
+
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct confdb_ctx *confdb;
+
+ struct sss_domain_info *local;
+
+ int lock;
+ int unlock;
+} PySssLocalObject;
+
+/*
+ * The transaction object
+ */
+struct py_sss_transaction {
+ PySssLocalObject *self;
+ struct ops_ctx *ops;
+
+ struct sysdb_handle *handle;
+ bool transaction_done;
+ int error;
+};
+
+/*
+ * Error reporting
+ */
+static void PyErr_SetSssErrorWithMessage(int ret, const char *message)
+{
+ PyObject *exc = Py_BuildValue(discard_const_p(char, "(is)"),
+ ret, message);
+
+ PyErr_SetObject(PyExc_IOError, exc);
+ Py_XDECREF(exc);
+}
+
+static void PyErr_SetSssError(int ret)
+{
+ PyErr_SetSssErrorWithMessage(ret, strerror(ret));
+}
+
+/*
+ * Common init of all methods
+ */
+struct ops_ctx *init_ctx(PySssLocalObject *self)
+{
+ struct ops_ctx *ops = NULL;
+
+ ops = talloc_zero(self->mem_ctx, struct ops_ctx);
+ if (ops == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ ops->domain = self->local;
+ return ops;
+}
+
+/*
+ * Common transaction finish
+ */
+static void req_done(struct tevent_req *req)
+{
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+
+ trs->error = sysdb_transaction_commit_recv(req);
+ trs->transaction_done = true;
+}
+
+/*
+ * Add a user
+ */
+static void py_sss_useradd_transaction(struct tevent_req *req);
+
+PyDoc_STRVAR(py_sss_useradd__doc__,
+ "Add a user named ``username``.\n\n"
+ ":param username: name of the user\n\n"
+ ":param kwargs: Keyword arguments ro customize the operation\n\n"
+ "* useradd can be customized further with keyword arguments:\n"
+ " * ``uid``: The UID of the user\n"
+ " * ``gid``: The GID of the user\n"
+ " * ``gecos``: The comment string\n"
+ " * ``homedir``: Home directory\n"
+ " * ``shell``: Login shell\n"
+ " * ``groups``: List of groups the user is member of\n");
+
+
+static PyObject *py_sss_useradd(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct py_sss_transaction *trs = NULL;
+ struct tevent_req *req;
+ unsigned long uid = 0;
+ unsigned long gid = 0;
+ const char *gecos = NULL;
+ const char *home = NULL;
+ const char *shell = NULL;
+ char *username = NULL;
+ int ret;
+ const char * const kwlist[] = { "username", "uid", "gid", "gecos",
+ "homedir", "shell", "groups", NULL };
+ PyObject *py_groups = Py_None;
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|kksssO!",
+ discard_const_p(char *, kwlist),
+ &username,
+ &uid,
+ &gid,
+ &gecos,
+ &home,
+ &shell,
+ &PyList_Type,
+ &py_groups)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ if (py_groups != Py_None) {
+ ops->addgroups = PyList_AsStringList(self->mem_ctx, py_groups, "groups");
+ if (!ops->addgroups) {
+ return NULL;
+ }
+ }
+
+ ops->name = username;
+ ops->uid = uid;
+
+ /* fill in defaults */
+ ret = useradd_defaults(self->mem_ctx,
+ self->confdb,
+ ops, gecos,
+ home, shell);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+
+ /* add the user within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_useradd_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_useradd_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+
+ /* useradd */
+ ret = useradd(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*
+ * Delete a user
+ */
+static void py_sss_userdel_transaction(struct tevent_req *);
+
+PyDoc_STRVAR(py_sss_userdel__doc__,
+ "Remove the user named ``username``.\n\n"
+ ":param username: Name of user being removed\n");
+
+static PyObject *py_sss_userdel(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct tevent_req *req;
+ struct py_sss_transaction *trs = NULL;
+ char *username = NULL;
+ int ret;
+
+ if(!PyArg_ParseTuple(args, "s", &username)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ ops->name = username;
+
+ /* delete the user within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_userdel_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_userdel_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+
+ /* userdel */
+ ret = userdel(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*
+ * Modify a user
+ */
+static void py_sss_usermod_transaction(struct tevent_req *);
+
+PyDoc_STRVAR(py_sss_usermod__doc__,
+ "Modify a user.\n\n"
+ ":param username: Name of user being modified\n\n"
+ ":param kwargs: Keyword arguments ro customize the operation\n\n"
+ "* usermod can be customized further with keyword arguments:\n"
+ " * ``uid``: The UID of the user\n"
+ " * ``gid``: The GID of the user\n"
+ " * ``gecos``: The comment string\n"
+ " * ``homedir``: Home directory\n"
+ " * ``shell``: Login shell\n"
+ " * ``addgroups``: List of groups to add the user to\n"
+ " * ``rmgroups``: List of groups to remove the user from\n"
+ " * ``lock``: Lock or unlock the account\n");
+
+static PyObject *py_sss_usermod(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct tevent_req *req;
+ struct py_sss_transaction *trs = NULL;
+ int ret;
+ PyObject *py_addgroups = Py_None;
+ PyObject *py_rmgroups = Py_None;
+ unsigned long uid = 0;
+ unsigned long gid = 0;
+ char *gecos = NULL;
+ char *home = NULL;
+ char *shell = NULL;
+ char *username = NULL;
+ unsigned long lock = 0;
+ const char * const kwlist[] = { "username", "uid", "gid", "lock",
+ "gecos", "homedir", "shell",
+ "addgroups", "rmgroups", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|kkksssO!O!",
+ discard_const_p(char *, kwlist),
+ &username,
+ &uid,
+ &gid,
+ &lock,
+ &gecos,
+ &home,
+ &shell,
+ &PyList_Type,
+ &py_addgroups,
+ &PyList_Type,
+ &py_rmgroups)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ if (lock && lock != DO_LOCK && lock != DO_UNLOCK) {
+ PyErr_SetString(PyExc_ValueError,
+ "Unkown value for lock parameter");
+ goto fail;
+ }
+
+ if (py_addgroups != Py_None) {
+ ops->addgroups = PyList_AsStringList(self->mem_ctx,
+ py_addgroups,
+ "addgroups");
+ if (!ops->addgroups) {
+ return NULL;
+ }
+ }
+
+ if (py_rmgroups != Py_None) {
+ ops->rmgroups = PyList_AsStringList(self->mem_ctx,
+ py_rmgroups,
+ "rmgroups");
+ if (!ops->rmgroups) {
+ return NULL;
+ }
+ }
+
+ ops->name = username;
+ ops->uid = uid;
+ ops->gid = gid;
+ ops->gecos = gecos;
+ ops->home = home;
+ ops->shell = shell;
+ ops->lock = lock;
+
+ /* modify the user within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_usermod_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_usermod_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+ /* usermod */
+ ret = usermod(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*
+ * Add a group
+ */
+static void py_sss_groupadd_transaction(struct tevent_req *);
+
+PyDoc_STRVAR(py_sss_groupadd__doc__,
+ "Add a group.\n\n"
+ ":param groupname: Name of group being added\n\n"
+ ":param kwargs: Keyword arguments ro customize the operation\n\n"
+ "* groupmod can be customized further with keyword arguments:\n"
+ " * ``gid``: The GID of the group\n");
+
+static PyObject *py_sss_groupadd(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct tevent_req *req;
+ struct py_sss_transaction *trs = NULL;
+ char *groupname;
+ unsigned long gid = 0;
+ int ret;
+ const char * const kwlist[] = { "groupname", "gid", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|k",
+ discard_const_p(char *, kwlist),
+ &groupname,
+ &gid)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ ops->name = groupname;
+ ops->gid = gid;
+
+ /* add the group within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_groupadd_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_groupadd_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+
+ /* groupadd */
+ ret = groupadd(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*
+ * Delete a group
+ */
+static void py_sss_groupdel_transaction(struct tevent_req *req);
+
+PyDoc_STRVAR(py_sss_groupdel__doc__,
+ "Remove a group.\n\n"
+ ":param groupname: Name of group being removed\n");
+
+static PyObject *py_sss_groupdel(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct tevent_req *req;
+ struct py_sss_transaction *trs = NULL;
+ char *groupname = NULL;
+ int ret;
+
+ if(!PyArg_ParseTuple(args, "s", &groupname)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ ops->name = groupname;
+
+ /* delete the group within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_groupdel_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_groupdel_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+
+ /* groupdel */
+ ret = groupdel(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*
+ * Modify a group
+ */
+static void py_sss_groupmod_transaction(struct tevent_req *);
+
+PyDoc_STRVAR(py_sss_groupmod__doc__,
+"Modify a group.\n\n"
+":param groupname: Name of group being modified\n\n"
+":param kwargs: Keyword arguments ro customize the operation\n\n"
+"* groupmod can be customized further with keyword arguments:\n"
+" * ``gid``: The GID of the group\n\n"
+" * ``addgroups``: Groups to add the group to\n\n"
+" * ``rmgroups``: Groups to remove the group from\n\n");
+
+static PyObject *py_sss_groupmod(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct ops_ctx *ops = NULL;
+ struct tevent_req *req;
+ struct py_sss_transaction *trs = NULL;
+ int ret;
+ PyObject *py_addgroups = Py_None;
+ PyObject *py_rmgroups = Py_None;
+ unsigned long gid = 0;
+ char *groupname = NULL;
+ const char * const kwlist[] = { "groupname", "gid", "addgroups",
+ "rmgroups", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|kO!O!",
+ discard_const_p(char *, kwlist),
+ &groupname,
+ &gid,
+ &PyList_Type,
+ &py_addgroups,
+ &PyList_Type,
+ &py_rmgroups)) {
+ goto fail;
+ }
+
+ ops = init_ctx(self);
+ if (!ops) {
+ return NULL;
+ }
+
+ if (py_addgroups != Py_None) {
+ ops->addgroups = PyList_AsStringList(self->mem_ctx,
+ py_addgroups,
+ "addgroups");
+ if (!ops->addgroups) {
+ return NULL;
+ }
+ }
+
+ if (py_rmgroups != Py_None) {
+ ops->rmgroups = PyList_AsStringList(self->mem_ctx,
+ py_rmgroups,
+ "rmgroups");
+ if (!ops->rmgroups) {
+ return NULL;
+ }
+ }
+
+ ops->name = groupname;
+ ops->gid = gid;
+
+ /* modify the group within a sysdb transaction */
+ trs = talloc_zero(self->mem_ctx, struct py_sss_transaction);
+ if (!trs) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ trs->self = self;
+ trs->ops = ops;
+
+ req = sysdb_transaction_send(self->mem_ctx, self->ev, self->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction"));
+ PyErr_NoMemory();
+ goto fail;
+ }
+ tevent_req_set_callback(req, py_sss_groupmod_transaction, trs);
+
+ TRANSACTION_WAIT(trs, ret);
+
+
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(ops);
+ talloc_zfree(trs);
+ return NULL;
+}
+
+static void py_sss_groupmod_transaction(struct tevent_req *req)
+{
+ int ret;
+ struct py_sss_transaction *trs = tevent_req_callback_data(req,
+ struct py_sss_transaction);
+ struct tevent_req *subreq;
+
+ ret = sysdb_transaction_recv(req, trs, &trs->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(req);
+
+ /* groupmod */
+ ret = groupmod(trs->self->mem_ctx, trs->self->ev,
+ trs->self->sysdb, trs->handle, trs->ops);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_commit_send(trs->self->mem_ctx, trs->self->ev, trs->handle);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, req_done, trs);
+ return;
+
+fail:
+ /* free transaction and signal error */
+ talloc_zfree(trs->handle);
+ trs->transaction_done = true;
+ trs->error = ret;
+}
+
+/*** python plumbing begins here ***/
+
+/*
+ * The sss.local destructor
+ */
+static void PySssLocalObject_dealloc(PySssLocalObject *self)
+{
+ talloc_free(self->mem_ctx);
+ self->ob_type->tp_free((PyObject*) self);
+}
+
+/*
+ * The sss.local constructor
+ */
+static PyObject *PySssLocalObject_new(PyTypeObject *type,
+ PyObject *args,
+ PyObject *kwds)
+{
+ TALLOC_CTX *mem_ctx;
+ PySssLocalObject *self;
+ char *confdb_path;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ self = (PySssLocalObject *) type->tp_alloc(type, 0);
+ if (self == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ self->mem_ctx = mem_ctx;
+
+ self->ev = tevent_context_init(mem_ctx);
+ if (self->ev == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(EIO, "Cannot create event context");
+ return NULL;
+ }
+
+ confdb_path = talloc_asprintf(self->mem_ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (confdb_path == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ /* Connect to the conf db */
+ ret = confdb_init(self->mem_ctx, &self->confdb, confdb_path);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret,
+ "Could not initialize connection to the confdb\n");
+ return NULL;
+ }
+
+ ret = confdb_get_domain(self->confdb, "local", &self->local);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret, "Cannot get local domain");
+ return NULL;
+ }
+
+ /* open 'local' sysdb at default path */
+ ret = sysdb_domain_init(self->mem_ctx, self->ev, self->local, DB_PATH, &self->sysdb);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret,
+ "Could not initialize connection to the sysdb\n");
+ return NULL;
+ }
+
+ self->lock = DO_LOCK;
+ self->unlock = DO_UNLOCK;
+
+ return (PyObject *) self;
+}
+
+/*
+ * sss.local object methods
+ */
+static PyMethodDef sss_local_methods[] = {
+ { "useradd", (PyCFunction) py_sss_useradd,
+ METH_KEYWORDS, py_sss_useradd__doc__
+ },
+ { "userdel", (PyCFunction) py_sss_userdel,
+ METH_VARARGS, py_sss_userdel__doc__
+ },
+ { "usermod", (PyCFunction) py_sss_usermod,
+ METH_KEYWORDS, py_sss_usermod__doc__
+ },
+ { "groupadd", (PyCFunction) py_sss_groupadd,
+ METH_KEYWORDS, py_sss_groupadd__doc__
+ },
+ { "groupdel", (PyCFunction) py_sss_groupdel,
+ METH_KEYWORDS, py_sss_groupdel__doc__
+ },
+ { "groupmod", (PyCFunction) py_sss_groupmod,
+ METH_KEYWORDS, py_sss_groupmod__doc__
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+static PyMemberDef sss_members[] = {
+ { discard_const_p(char, "lock"), T_INT,
+ offsetof(PySssLocalObject, lock), RO },
+ { discard_const_p(char, "unlock"), T_INT,
+ offsetof(PySssLocalObject, unlock), RO },
+ {NULL} /* Sentinel */
+};
+
+/*
+ * sss.local object properties
+ */
+static PyTypeObject pysss_local_type = {
+ PyObject_HEAD_INIT(NULL)
+ .tp_name = "sss.local",
+ .tp_basicsize = sizeof(PySssLocalObject),
+ .tp_new = PySssLocalObject_new,
+ .tp_dealloc = (destructor) PySssLocalObject_dealloc,
+ .tp_methods = sss_local_methods,
+ .tp_members = sss_members,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "SSS DB manipulation",
+};
+
+/*
+ * Module methods
+ */
+static PyMethodDef module_methods[] = {
+ {NULL} /* Sentinel */
+};
+
+/*
+ * Module initialization
+ */
+PyMODINIT_FUNC
+initpysss(void)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&pysss_local_type) < 0)
+ return;
+
+ m = Py_InitModule("pysss", module_methods);
+ if (m == NULL)
+ return;
+
+ Py_INCREF(&pysss_local_type);
+ PyModule_AddObject(m, "local", (PyObject *)&pysss_local_type);
+}
+
diff --git a/server/tests/python-test.py b/server/tests/python-test.py
new file mode 100644
index 000000000..fddf9c311
--- /dev/null
+++ b/server/tests/python-test.py
@@ -0,0 +1,391 @@
+#!/usr/bin/python
+#coding=utf-8
+
+# Authors:
+# Jakub Hrozek <jhrozek@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import unittest
+import commands
+import errno
+
+# module under test
+import pysss
+
+class LocalTest(unittest.TestCase):
+ local_path = "/var/lib/sss/db/sssd.ldb"
+
+ def setUp(self):
+ self.local = pysss.local()
+
+ def _run_and_check(self, runme):
+ (status, output) = commands.getstatusoutput(runme)
+ self.failUnlessEqual(status, 0, output)
+
+ def _get_object_info(self, name, subtree, domain):
+ search_dn = "dn=name=%s,cn=%s,cn=%s,cn=sysdb" % (name, subtree, domain)
+ (status, output) = commands.getstatusoutput("ldbsearch -H %s %s" % (self.local_path,search_dn))
+
+ if status: return {}
+
+ kw = {}
+ for key, value in [ l.split(':') for l in output.split('\n') if ":" in l ]:
+ kw[key] = value.strip()
+
+ del kw['asq']
+ return kw
+
+ def get_user_info(self, name, domain="LOCAL"):
+ return self._get_object_info(name, "users", domain)
+
+ def get_group_info(self, name, domain="LOCAL"):
+ return self._get_object_info(name, "groups", domain)
+
+ def _validate_object(self, kw, name, **kwargs):
+ if kw == {}: self.fail("Could not get %s info" % name)
+ for key in kwargs.keys():
+ self.assert_(str(kwargs[key]) == str(kw[key]), "%s %s != %s %s" % (key, kwargs[key], key, kw[key]))
+
+ def validate_user(self, username, **kwargs):
+ return self._validate_object(self.get_user_info(username), "user", **kwargs)
+
+ def validate_group(self, groupname, **kwargs):
+ return self._validate_object(self.get_group_info(groupname), "group", **kwargs)
+
+ def _validate_no_object(self, kw, name):
+ if kw != {}:
+ self.fail("Got %s info" % name)
+
+ def validate_no_user(self, username):
+ return self._validate_no_object(self.get_user_info(username), "user")
+
+ def validate_no_group(self, groupname):
+ return self._validate_no_object(self.get_group_info(groupname), "group")
+
+ def _get_object_membership(self, name, subtree, domain):
+ search_dn = "dn=name=%s,cn=%s,cn=%s,cn=sysdb" % (name, subtree, domain)
+ (status, output) = commands.getstatusoutput("ldbsearch -H %s %s" % (self.local_path,search_dn))
+
+ if status:
+ return []
+
+ members = [ value.strip() for key, value in [ l.split(':') for l in output.split('\n') if ":" in l ] if key == "memberof" ]
+ return members
+
+ def _assertMembership(self, name, group_list, subtree, domain):
+ members = self._get_object_membership(name, subtree, domain)
+ for group in group_list:
+ group_dn = "name=%s,cn=groups,cn=%s,cn=sysdb" % (group, domain)
+ if group_dn in members:
+ members.remove(group_dn)
+ else:
+ self.fail("Cannot find required group %s" % group_dn)
+
+ if len(members) > 0:
+ self.fail("More groups than selected")
+
+ def assertUserMembership(self, name, group_list, domain="LOCAL"):
+ return self._assertMembership(name, group_list, "users", domain)
+
+ def assertGroupMembership(self, name, group_list, domain="LOCAL"):
+ return self._assertMembership(name, group_list, "groups", domain)
+
+ def get_user_membership(self, name, domain="LOCAL"):
+ return self._get_object_membership(name, "users", domain)
+
+ def get_group_membership(self, name, domain="LOCAL"):
+ return self._get_object_membership(name, "groups", domain)
+
+ def add_group(self, groupname):
+ self._run_and_check("sss_groupadd %s" % (groupname))
+
+ def remove_group(self, groupname):
+ self._run_and_check("sss_groupdel %s" % (groupname))
+
+ def add_user(self, username):
+ self._run_and_check("sss_useradd %s" % (username))
+
+ def remove_user(self, username):
+ self._run_and_check("sss_userdel %s" % (username))
+
+class SanityTest(unittest.TestCase):
+ def testInstantiate(self):
+ "Test that the local backed binding can be instantiated"
+ local = pysss.local()
+ self.assert_(local.__class__, "<type 'sss.local'>")
+
+class UseraddTest(LocalTest):
+ def tearDown(self):
+ if self.username:
+ self.remove_user(self.username)
+
+ def testUseradd(self):
+ "Test adding a local user"
+ self.username = "testUseradd"
+ self.local.useradd(self.username)
+ self.validate_user(self.username)
+
+ def testUseraddWithParams(self):
+ "Test adding a local user with modified parameters"
+ self.username = "testUseraddWithParams"
+ self.local.useradd(self.username,
+ gecos="foo bar",
+ homedir="/people/foobar",
+ shell="/bin/zsh")
+ self.validate_user(self.username,
+ gecos="foo bar",
+ homeDirectory="/people/foobar",
+ loginShell="/bin/zsh")
+
+ def testUseraddToGroups(self):
+ "Test adding a local user with group membership"
+ self.username = "testUseraddToGroups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+ try:
+ self.local.useradd(self.username,
+ groups=["gr1","gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1","gr2"])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+ def testUseraddWithUID(self):
+ "Test adding a local user with a custom UID"
+ self.username = "testUseraddWithUID"
+ self.local.useradd(self.username,
+ uid=1024)
+ self.validate_user(self.username,
+ uidNumber=1024)
+
+class UseraddTestNegative(LocalTest):
+ def testUseraddNoParams(self):
+ "Test that local.useradd() requires the username parameter"
+ self.assertRaises(TypeError, self.local.useradd)
+
+ def testUseraddUserAlreadyExists(self):
+ "Test adding a local with a duplicite name"
+ self.username = "testUseraddUserAlreadyExists"
+ self.local.useradd(self.username)
+ try:
+ self.local.useradd(self.username)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_user(self.username)
+
+ def testUseraddUIDAlreadyExists(self):
+ "Test adding a local with a duplicite user ID"
+ self.username = "testUseraddUIDAlreadyExists1"
+ self.local.useradd(self.username, uid=1025)
+ try:
+ self.local.useradd("testUseraddUIDAlreadyExists2", uid=1025)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_user(self.username)
+
+class UserdelTest(LocalTest):
+ def testUserdel(self):
+ self.add_user("testUserdel")
+ self.validate_user("testUserdel")
+ self.local.userdel("testUserdel")
+ self.validate_no_user("testUserdel")
+
+ def testUserdelNegative(self):
+ self.validate_no_user("testUserdelNegative")
+ try:
+ self.local.userdel("testUserdelNegative")
+ except IOError, e:
+ self.assertEquals(e.errno, errno.ENOENT)
+ else:
+ fail("Was expecting exception")
+
+class UsermodTest(LocalTest):
+ def setUp(self):
+ self.local = pysss.local()
+ self.username = "UsermodTest"
+ self.add_user(self.username)
+
+ def tearDown(self):
+ self.remove_user(self.username)
+
+ def testUsermod(self):
+ "Test modifying user attributes"
+ self.local.usermod(self.username,
+ gecos="foo bar",
+ homedir="/people/foobar",
+ shell="/bin/zsh")
+ self.validate_user(self.username,
+ gecos="foo bar",
+ homeDirectory="/people/foobar",
+ loginShell="/bin/zsh")
+
+ def testUsermodUID(self):
+ "Test modifying UID"
+ self.local.usermod(self.username,
+ uid=1024)
+ self.validate_user(self.username,
+ uidNumber=1024)
+
+ def testUsermodGroupMembership(self):
+ "Test adding to and removing from groups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+
+ try:
+ self.local.usermod(self.username,
+ addgroups=["gr1","gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1","gr2"])
+ self.local.usermod(self.username,
+ rmgroups=["gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1"])
+ self.local.usermod(self.username,
+ rmgroups=["gr1"])
+ self.assertUserMembership(self.username,
+ [])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+ def testUsermodLockUnlock(self):
+ "Test locking and unlocking user"
+ self.local.usermod(self.username,
+ lock=self.local.lock)
+ self.validate_user(self.username,
+ disabled="true")
+ self.local.usermod(self.username,
+ lock=self.local.unlock)
+ self.validate_user(self.username,
+ disabled="false")
+
+class GroupaddTest(LocalTest):
+ def tearDown(self):
+ if self.groupname:
+ self.remove_group(self.groupname)
+
+ def testGroupadd(self):
+ "Test adding a local group"
+ self.groupname = "testGroupadd"
+ self.local.groupadd(self.groupname)
+ self.validate_group(self.groupname)
+
+ def testGroupaddWithGID(self):
+ "Test adding a local group with a custom GID"
+ self.groupname = "testUseraddWithGID"
+ self.local.groupadd(self.groupname,
+ gid=1024)
+ self.validate_group(self.groupname,
+ gidNumber=1024)
+
+class GroupaddTestNegative(LocalTest):
+ def testGroupaddNoParams(self):
+ "Test that local.groupadd() requires the groupname parameter"
+ self.assertRaises(TypeError, self.local.groupadd)
+
+ def testGroupaddUserAlreadyExists(self):
+ "Test adding a local with a duplicite name"
+ self.groupname = "testGroupaddUserAlreadyExists"
+ self.local.groupadd(self.groupname)
+ try:
+ self.local.groupadd(self.groupname)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_group(self.groupname)
+
+ def testGroupaddGIDAlreadyExists(self):
+ "Test adding a local with a duplicite group ID"
+ self.groupname = "testGroupaddGIDAlreadyExists1"
+ self.local.groupadd(self.groupname, gid=1025)
+ try:
+ self.local.groupadd("testGroupaddGIDAlreadyExists2", gid=1025)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_group(self.groupname)
+
+class GroupdelTest(LocalTest):
+ def testGroupdel(self):
+ self.add_group("testGroupdel")
+ self.validate_group("testGroupdel")
+ self.local.groupdel("testGroupdel")
+ self.validate_no_group("testGroupdel")
+
+ def testGroupdelNegative(self):
+ self.validate_no_group("testGroupdelNegative")
+ try:
+ self.local.groupdel("testGroupdelNegative")
+ except IOError, e:
+ self.assertEquals(e.errno, errno.ENOENT)
+ else:
+ fail("Was expecting exception")
+
+
+class GroupmodTest(LocalTest):
+ def setUp(self):
+ self.local = pysss.local()
+ self.groupname = "GroupmodTest"
+ self.add_group(self.groupname)
+
+ def tearDown(self):
+ self.remove_group(self.groupname)
+
+ def testGroupmodGID(self):
+ "Test modifying UID"
+ self.local.groupmod(self.groupname,
+ gid=1024)
+ self.validate_group(self.groupname,
+ gidNumber=1024)
+
+ def testGroupmodGroupMembership(self):
+ "Test adding to groups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+ try:
+ self.local.groupmod(self.groupname,
+ addgroups=["gr1","gr2"])
+ self.assertGroupMembership(self.groupname,
+ ["gr1","gr2"])
+ self.local.groupmod(self.groupname,
+ rmgroups=["gr2"])
+ self.assertGroupMembership(self.groupname,
+ ["gr1"])
+ self.local.groupmod(self.groupname,
+ rmgroups=["gr1"])
+ self.assertGroupMembership(self.groupname,
+ [])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+# -------------- run the test suite -------------- #
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h
index b6509c22c..c63b90332 100644
--- a/server/tools/tools_util.h
+++ b/server/tools/tools_util.h
@@ -23,6 +23,8 @@
#ifndef __TOOLS_UTIL_H__
#define __TOOLS_UTIL_H__
+#include <popt.h>
+
#include "util/sssd-i18n.h"
#include "tools/sss_sync_ops.h"
diff --git a/server/util/util.h b/server/util/util.h
index dda7527ee..ea7f44e83 100644
--- a/server/util/util.h
+++ b/server/util/util.h
@@ -104,6 +104,14 @@ void debug_fn(const char *format, ...);
#define talloc_zfree(ptr) do { talloc_free(ptr); ptr = NULL; } while(0)
#endif
+#ifndef discard_const_p
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr)))
+#else
+# define discard_const_p(type, ptr) ((type *)(ptr))
+#endif
+#endif
+
/* TODO: remove later
* These functions are available in the latest tevent and are the ones that
* should be used as tevent_req is rightfully opaque there */