samba-tool tests: add tests for contact management
authorBjörn Baumbach <bb@sernet.de>
Wed, 20 Mar 2019 16:17:05 +0000 (17:17 +0100)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 4 Jul 2019 02:07:21 +0000 (02:07 +0000)
Signed-off-by: Björn Baumbach <bb@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/samba_tool/contact.py [new file with mode: 0644]
python/samba/tests/samba_tool/contact_edit.sh [new file with mode: 0755]
source4/selftest/tests.py

diff --git a/python/samba/tests/samba_tool/contact.py b/python/samba/tests/samba_tool/contact.py
new file mode 100644 (file)
index 0000000..626277c
--- /dev/null
@@ -0,0 +1,319 @@
+# Unix SMB/CIFS implementation.
+#
+# Tests for samba-tool contact management commands
+#
+# Copyright (C) Bjoern Baumbach <bbaumbach@samba.org> 2019
+#
+# 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/>.
+#
+
+import os
+import ldb
+from samba.tests.samba_tool.base import SambaToolCmdTest
+
+class ContactCmdTestCase(SambaToolCmdTest):
+    """Tests for samba-tool contact subcommands"""
+    contacts = []
+    samdb = None
+
+    def setUp(self):
+        super(ContactCmdTestCase, self).setUp()
+        self.creds = "-U%s%%%s" % (os.environ["DC_USERNAME"],
+                                   os.environ["DC_PASSWORD"])
+        self.samdb = self.getSamDB("-H",
+                                   "ldap://%s" % os.environ["DC_SERVER"],
+                                   self.creds)
+        contact = None
+        self.contacts = []
+
+        contact = self._randomContact({"expectedname": "contact1",
+                                       "name": "contact1"})
+        self.contacts.append(contact)
+
+        # No 'name' is given here, so the name will be made from givenname.
+        contact = self._randomContact({"expectedname": "contact2",
+                                       "givenName": "contact2"})
+        self.contacts.append(contact)
+
+        contact = self._randomContact({"expectedname": "contact3",
+                                       "name": "contact3",
+                                       "displayName": "contact3displayname",
+                                       "givenName": "not_contact3",
+                                       "initials": "I",
+                                       "sn": "not_contact3",
+                                       "mobile": "12345"})
+        self.contacts.append(contact)
+
+        # No 'name' is given here, so the name will be made from the the
+        # sn, initials and givenName attributes.
+        contact = self._randomContact({"expectedname": "James T. Kirk",
+                                       "sn": "Kirk",
+                                       "initials": "T",
+                                       "givenName": "James"})
+        self.contacts.append(contact)
+
+        # setup the 4 contacts and ensure they are correct
+        for contact in self.contacts:
+            (result, out, err) = self._create_contact(contact)
+
+            self.assertCmdSuccess(result, out, err)
+            self.assertNotIn(
+                "ERROR", err, "There shouldn't be any error message")
+            self.assertIn("Contact '%s' created successfully" %
+                          contact["expectedname"], out)
+
+            found = self._find_contact(contact["expectedname"])
+
+            self.assertIsNotNone(found)
+
+            contactname = contact["expectedname"]
+            self.assertEquals("%s" % found.get("name"), contactname)
+            self.assertEquals("%s" % found.get("description"),
+                              contact["description"])
+
+    def tearDown(self):
+        super(ContactCmdTestCase, self).tearDown()
+        # clean up all the left over contacts, just in case
+        for contact in self.contacts:
+            if self._find_contact(contact["expectedname"]):
+                (result, out, err) = self.runsubcmd(
+                    "contact", "delete", "%s" % contact["expectedname"])
+                self.assertCmdSuccess(result, out, err,
+                                      "Failed to delete contact '%s'" %
+                                      contact["expectedname"])
+
+    def test_newcontact(self):
+        """This tests the "contact create" and "contact delete" commands"""
+        # try to create all the contacts again, this should fail
+        for contact in self.contacts:
+            (result, out, err) = self._create_contact(contact)
+            self.assertCmdFail(result, "Succeeded to create existing contact")
+            self.assertIn("already exists", err)
+
+        # try to delete all the contacts we just created
+        for contact in self.contacts:
+            (result, out, err) = self.runsubcmd("contact", "delete", "%s" %
+                                                contact["expectedname"])
+            self.assertCmdSuccess(result, out, err,
+                                  "Failed to delete contact '%s'" %
+                                  contact["expectedname"])
+            found = self._find_contact(contact["expectedname"])
+            self.assertIsNone(found,
+                              "Deleted contact '%s' still exists" %
+                              contact["expectedname"])
+
+        # test creating contacts in an specified OU
+        parentou = self._randomOU({"name": "testOU"})
+        (result, out, err) = self._create_ou(parentou)
+        self.assertCmdSuccess(result, out, err)
+
+        for contact in self.contacts:
+            (result, out, err) = self._create_contact(contact, ou="OU=testOU")
+
+            self.assertCmdSuccess(result, out, err)
+            self.assertEquals(err, "", "There shouldn't be any error message")
+            self.assertIn("Contact '%s' created successfully" %
+                          contact["expectedname"], out)
+
+            found = self._find_contact(contact["expectedname"])
+
+            contactname = contact["expectedname"]
+            self.assertEquals("%s" % found.get("name"), contactname)
+            self.assertEquals("%s" % found.get("description"),
+                              contact["description"])
+
+        # try to delete all the contacts we just created, by DN
+        for contact in self.contacts:
+            expecteddn = ldb.Dn(self.samdb,
+                                "CN=%s,OU=%s,%s" %
+                                (contact["expectedname"],
+                                 parentou["name"],
+                                 self.samdb.domain_dn()))
+            (result, out, err) = self.runsubcmd("contact", "delete", "%s" %
+                                                expecteddn)
+            self.assertCmdSuccess(result, out, err,
+                                  "Failed to delete contact '%s'" %
+                                  contact["expectedname"])
+            found = self._find_contact(contact["expectedname"])
+            self.assertIsNone(found,
+                              "Deleted contact '%s' still exists" %
+                              contact["expectedname"])
+
+        (result, out, err) = self.runsubcmd("ou", "delete",
+                                            "OU=%s" % parentou["name"])
+        self.assertCmdSuccess(result, out, err,
+                              "Failed to delete ou '%s'" % parentou["name"])
+
+        # creating contacts, again for further tests
+        for contact in self.contacts:
+            (result, out, err) = self._create_contact(contact)
+
+            self.assertCmdSuccess(result, out, err)
+            self.assertEquals(err, "", "There shouldn't be any error message")
+            self.assertIn("Contact '%s' created successfully" %
+                          contact["expectedname"], out)
+
+            found = self._find_contact(contact["expectedname"])
+
+            contactname = contact["expectedname"]
+            self.assertEquals("%s" % found.get("name"), contactname)
+            self.assertEquals("%s" % found.get("description"),
+                              contact["description"])
+
+    def test_list(self):
+        (result, out, err) = self.runsubcmd("contact", "list")
+        self.assertCmdSuccess(result, out, err, "Error running list")
+
+        search_filter = "(objectClass=contact)"
+        contactlist = self.samdb.search(base=self.samdb.domain_dn(),
+                                         scope=ldb.SCOPE_SUBTREE,
+                                         expression=search_filter,
+                                         attrs=["name"])
+
+        self.assertTrue(len(contactlist) > 0, "no contacts found in samdb")
+
+        for contactobj in contactlist:
+            name = contactobj.get("name", idx=0)
+            self.assertMatch(out, str(name),
+                             "contact '%s' not found" % name)
+
+    def test_list_full_dn(self):
+        (result, out, err) = self.runsubcmd("contact", "list", "--full-dn")
+        self.assertCmdSuccess(result, out, err, "Error running list")
+
+        search_filter = "(objectClass=contact)"
+        contactlist = self.samdb.search(base=self.samdb.domain_dn(),
+                                         scope=ldb.SCOPE_SUBTREE,
+                                         expression=search_filter,
+                                         attrs=["dn"])
+
+        self.assertTrue(len(contactlist) > 0, "no contacts found in samdb")
+
+        for contactobj in contactlist:
+            self.assertMatch(out, str(contactobj.dn),
+                             "contact '%s' not found" % str(contactobj.dn))
+
+    def test_move(self):
+        parentou = self._randomOU({"name": "parentOU"})
+        (result, out, err) = self._create_ou(parentou)
+        self.assertCmdSuccess(result, out, err)
+
+        for contact in self.contacts:
+            olddn = self._find_contact(contact["expectedname"]).get("dn")
+
+            (result, out, err) = self.runsubcmd("contact", "move",
+                                                "%s" % contact["expectedname"],
+                                                "OU=%s" % parentou["name"])
+            self.assertCmdSuccess(result, out, err,
+                                  "Failed to move contact '%s'" %
+                                  contact["expectedname"])
+            self.assertEquals(err, "", "There shouldn't be any error message")
+            self.assertIn('Moved contact "%s"' % contact["expectedname"], out)
+
+            found = self._find_contact(contact["expectedname"])
+            self.assertNotEquals(found.get("dn"), olddn,
+                                 ("Moved contact '%s' still exists with the "
+                                  "same dn" % contact["expectedname"]))
+            contactname = contact["expectedname"]
+            newexpecteddn = ldb.Dn(self.samdb,
+                                   "CN=%s,OU=%s,%s" %
+                                   (contactname,
+                                    parentou["name"],
+                                    self.samdb.domain_dn()))
+            self.assertEquals(found.get("dn"), newexpecteddn,
+                              "Moved contact '%s' does not exist" %
+                              contact["expectedname"])
+
+            (result, out, err) = self.runsubcmd("contact", "move",
+                                                "%s" % contact["expectedname"],
+                                                "%s" % olddn.parent())
+            self.assertCmdSuccess(result, out, err,
+                                  "Failed to move contact '%s'" %
+                                  contact["expectedname"])
+
+        (result, out, err) = self.runsubcmd("ou", "delete",
+                                            "OU=%s" % parentou["name"])
+        self.assertCmdSuccess(result, out, err,
+                              "Failed to delete ou '%s'" % parentou["name"])
+
+    def _randomContact(self, base={}):
+        """Create a contact with random attribute values, you can specify base
+        attributes"""
+
+        # No name attributes are given here, because the object name will
+        # be made from the sn, givenName and initials attributes, if no name
+        # is given.
+        contact = {
+            "description": self.randomName(count=100),
+        }
+        contact.update(base)
+        return contact
+
+    def _randomOU(self, base={}):
+        """Create an ou with random attribute values, you can specify base
+        attributes."""
+
+        ou = {
+            "name": self.randomName(),
+            "description": self.randomName(count=100),
+        }
+        ou.update(base)
+        return ou
+
+    def _create_contact(self, contact, ou=None):
+        args = ""
+
+        if "name" in contact:
+            args += '{0}'.format(contact['name'])
+
+        args += ' {0}'.format(self.creds)
+
+        if ou is not None:
+            args += ' --ou={0}'.format(ou)
+
+        if "description" in contact:
+            args += ' --description={0}'.format(contact["description"])
+        if "sn" in contact:
+            args += ' --surname={0}'.format(contact["sn"])
+        if "initials" in contact:
+            args += ' --initials={0}'.format(contact["initials"])
+        if "givenName" in contact:
+            args += ' --given-name={0}'.format(contact["givenName"])
+        if "displayName" in contact:
+            args += ' --display-name={0}'.format(contact["displayName"])
+        if "mobile" in contact:
+            args += ' --mobile-number={0}'.format(contact["mobile"])
+
+        args = args.split()
+
+        return self.runsubcmd('contact', 'create', *args)
+
+    def _create_ou(self, ou):
+        return self.runsubcmd("ou",
+                              "create",
+                              "OU=%s" % ou["name"],
+                              "--description=%s" % ou["description"])
+
+    def _find_contact(self, name):
+        contactname = name
+        search_filter = ("(&(objectClass=contact)(name=%s))" %
+                         ldb.binary_encode(contactname))
+        contactlist = self.samdb.search(base=self.samdb.domain_dn(),
+                                        scope=ldb.SCOPE_SUBTREE,
+                                        expression=search_filter,
+                                        attrs=[])
+        if contactlist:
+            return contactlist[0]
+        else:
+            return None
diff --git a/python/samba/tests/samba_tool/contact_edit.sh b/python/samba/tests/samba_tool/contact_edit.sh
new file mode 100755 (executable)
index 0000000..ca38900
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/sh
+#
+# Test for 'samba-tool contact edit'
+
+if [ $# -lt 3 ]; then
+cat <<EOF
+Usage: contact_edit.sh SERVER USERNAME PASSWORD
+EOF
+exit 1;
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+
+STpath=$(pwd)
+. $STpath/testprogs/blackbox/subunit.sh
+
+display_name="Björn"
+display_name_b64="QmrDtnJu"
+display_name_new="Renamed Bjoern"
+# attribute value including control character
+# echo -e "test \a string" | base64
+display_name_con_b64="dGVzdCAHIHN0cmluZwo="
+
+tmpeditor=$(mktemp --suffix .sh -p $STpath/bin samba-tool-editor-XXXXXXXX)
+chmod +x $tmpeditor
+
+create_test_contact() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool \
+               contact create testcontact1 \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+# Test edit contact - add base64 attributes
+add_attribute_base64() {
+       # create editor.sh
+       cat >$tmpeditor <<EOF
+#!/usr/bin/env bash
+contact_ldif="\$1"
+
+grep -v '^$' \$contact_ldif > \${contact_ldif}.tmp
+echo "displayName:: $display_name_b64" >> \${contact_ldif}.tmp
+
+mv \${contact_ldif}.tmp \$contact_ldif
+EOF
+
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact edit \
+               testcontact1 --editor=$tmpeditor \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+get_attribute_base64() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact show \
+               testcontact1 --attributes=displayName \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+delete_attribute() {
+       # create editor.sh
+       cat >$tmpeditor <<EOF
+#!/usr/bin/env bash
+contact_ldif="\$1"
+
+grep -v '^displayName' \$contact_ldif >> \${contact_ldif}.tmp
+mv \${contact_ldif}.tmp \$contact_ldif
+EOF
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact edit \
+               testcontact1 --editor=$tmpeditor \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+# Test edit contact - add base64 attribute value including control character
+add_attribute_base64_control() {
+       # create editor.sh
+       cat >$tmpeditor <<EOF
+#!/usr/bin/env bash
+contact_ldif="\$1"
+
+grep -v '^$' \$contact_ldif > \${contact_ldif}.tmp
+echo "displayName:: $display_name_con_b64" >> \${contact_ldif}.tmp
+
+mv \${contact_ldif}.tmp \$contact_ldif
+EOF
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact edit \
+               testcontact1 --editor=$tmpeditor \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+get_attribute_base64_control() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact show \
+               testcontact1 --attributes=displayName \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+
+# Test edit contact - change base64 attribute value including control character
+change_attribute_base64_control() {
+       # create editor.sh
+       cat >$tmpeditor <<EOF
+#!/usr/bin/env bash
+contact_ldif="\$1"
+
+sed -i -e 's/displayName:: $display_name_con_b64/displayName: $display_name/' \
+       \$contact_ldif
+EOF
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact edit \
+               testcontact1 --editor=$tmpeditor \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+get_attribute_base64_control() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact show \
+               testcontact1 --attributes=displayName \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+# Test edit contact - change attributes with LDB_FLAG_FORCE_NO_BASE64_LDIF
+change_attribute_force_no_base64() {
+       # create editor.sh
+       # Expects that the original attribute is available as clear text,
+       # because the LDB_FLAG_FORCE_NO_BASE64_LDIF should be used here.
+       cat >$tmpeditor <<EOF
+#!/usr/bin/env bash
+contact_ldif="\$1"
+
+sed -i -e 's/displayName: $display_name/displayName: $display_name_new/' \
+       \$contact_ldif
+EOF
+
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact edit \
+               testcontact1 --editor=$tmpeditor \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+get_changed_attribute_force_no_base64() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool contact show \
+                testcontact1 --attributes=displayName \
+                -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+delete_contact() {
+       $PYTHON ${STpath}/source4/scripting/bin/samba-tool \
+               contact delete testcontact1 \
+               -H "ldap://$SERVER" "-U$USERNAME" "--password=$PASSWORD"
+}
+
+failed=0
+
+testit "create_test_contact" create_test_contact || failed=`expr $failed + 1`
+testit "add_attribute_base64" add_attribute_base64 || failed=`expr $failed + 1`
+testit_grep "get_attribute_base64" "^displayName:: $display_name_b64" get_attribute_base64 || failed=`expr $failed + 1`
+testit "delete_attribute" delete_attribute || failed=`expr $failed + 1`
+testit "add_attribute_base64_control" add_attribute_base64_control || failed=`expr $failed + 1`
+testit_grep "get_attribute_base64_control" "^displayName:: $display_name_con_b64" get_attribute_base64_control || failed=`expr $failed + 1`
+testit "change_attribute_base64_control" change_attribute_base64_control || failed=`expr $failed + 1`
+testit_grep "get_attribute_base64_control" "^displayName:: $display_name_b64" get_attribute_base64_control || failed=`expr $failed + 1`
+testit "change_attribute_force_no_base64" change_attribute_force_no_base64 || failed=`expr $failed + 1`
+testit_grep "get_changed_attribute_force_no_base64" "^displayName: $display_name_new" get_changed_attribute_force_no_base64 || failed=`expr $failed + 1`
+testit "delete_contact" delete_contact || failed=`expr $failed + 1`
+
+rm -f $tmpeditor
+
+exit $failed
index 67e33fc37dcf26b35abc4bbe1a840069f3ca3938..6216ee52150d59a127fdff7429b015f28652aa82 100755 (executable)
@@ -659,11 +659,12 @@ planpythontestsuite("none", "samba.tests.samba_tool.visualize")
 for env in all_fl_envs:
     planpythontestsuite(env + ":local", "samba.tests.samba_tool.fsmo")
 
-# test samba-tool user, group and computer edit command
+# test samba-tool user, group, contact and computer edit command
 for env in all_fl_envs:
     env += ":local"
     plantestsuite("samba.tests.samba_tool.user_edit", env, [os.path.join(srcdir(), "python/samba/tests/samba_tool/user_edit.sh"), '$SERVER', '$USERNAME', '$PASSWORD'])
     plantestsuite("samba.tests.samba_tool.group_edit", env, [os.path.join(srcdir(), "python/samba/tests/samba_tool/group_edit.sh"), '$SERVER', '$USERNAME', '$PASSWORD'])
+    plantestsuite("samba.tests.samba_tool.contact_edit", env, [os.path.join(srcdir(), "python/samba/tests/samba_tool/contact_edit.sh"), '$SERVER', '$USERNAME', '$PASSWORD'])
     plantestsuite("samba.tests.samba_tool.computer_edit", env, [os.path.join(srcdir(), "python/samba/tests/samba_tool/computer_edit.sh"), '$SERVER', '$USERNAME', '$PASSWORD'])
 
 # We run this test against both AD DC implementations because it is