[SCM] Samba Shared Repository - branch master updated

Andrew Bartlett abartlet at samba.org
Sat Jun 9 15:43:03 UTC 2018


The branch, master has been updated
       via  81f7ecc dsdb: Use ldb_init() to make the ldb_context in dsdb audit tests
       via  1b07f13 dsdb: add defines for sessionInfo and networkSessionInfo
       via  d4deb80 dsdb: Audit group membership changes
       via  1c0f743 dsdb: audit samdb and password changes
      from  7ddbf60 s4-heimdal: Fix the format-truncation errors.

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 81f7ecc2a7fa1d44ef9a1f310eaf890c8fb8b158
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Jun 7 09:50:15 2018 +0200

    dsdb: Use ldb_init() to make the ldb_context in dsdb audit tests
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    
    Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
    Autobuild-Date(master): Sat Jun  9 17:42:38 CEST 2018 on sn-devel-144

commit 1b07f1337291409688815e5261921ee4f557ab7d
Author: Gary Lockyer <gary at catalyst.net.nz>
Date:   Thu May 31 15:12:46 2018 +1200

    dsdb: add defines for sessionInfo and networkSessionInfo
    
    Replace uses of the string "sessionInfo" with the constant
    DSDB_SESSION_INFO, and "networkSessionInfo" with the constant
    DSDB_NETWORK_SESSION_INFO.
    
    Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit d4deb800e2f472a91752b764918ffa49b878073d
Author: Gary Lockyer <gary at catalyst.net.nz>
Date:   Mon Apr 16 14:03:14 2018 +1200

    dsdb: Audit group membership changes
    
    Log details of Group membership changes and User Primary Group changes.
    Changes are logged in human readable and if samba has been built with
    JANSSON support in JSON format.
    
    Replicated updates are not logged.
    
    Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 1c0f743c2d51c74b146d5ceea3252683450f639f
Author: Gary Lockyer <gary at catalyst.net.nz>
Date:   Wed Apr 4 11:59:41 2018 +1200

    dsdb: audit samdb and password changes
    
    Add audit logging of DSDB operations and password changes, log messages
    are logged in human readable format and if samba is commpile with
    JANSSON support in JSON format.
    
    Log:
      * Details all DSDB add, modify and delete operations. Logs
        attributes, values, session details, transaction id.
      * Transaction roll backs.
      * Prepare commit and commit failures.
      * Summary details of replicated updates.
    
    Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

-----------------------------------------------------------------------

Summary of changes:
 lib/ldb-samba/ldb_ildap.c                          |    5 +-
 lib/ldb-samba/ldb_matching_rules.c                 |    2 +-
 lib/ldb-samba/ldb_wrap.c                           |    3 +-
 lib/ldb-samba/pyldb.c                              |    3 +-
 lib/ldb-samba/samba_extensions.c                   |    7 +-
 python/samba/tests/audit_log_base.py               |  177 ++
 python/samba/tests/audit_log_dsdb.py               |  608 ++++++
 python/samba/tests/audit_log_pass_change.py        |  324 +++
 python/samba/tests/group_audit.py                  |  355 ++++
 selftest/target/Samba4.pm                          |    6 +
 source4/dns_server/dlz_bind9.c                     |   10 +-
 source4/dns_server/dns_update.c                    |   17 +-
 source4/dsdb/common/util.h                         |    6 +
 source4/dsdb/samdb/ldb_modules/acl_util.c          |   12 +-
 source4/dsdb/samdb/ldb_modules/audit_log.c         | 1560 ++++++++++++++
 source4/dsdb/samdb/ldb_modules/audit_util.c        |  596 ++++++
 source4/dsdb/samdb/ldb_modules/descriptor.c        |    2 +-
 source4/dsdb/samdb/ldb_modules/group_audit.c       | 1362 ++++++++++++
 source4/dsdb/samdb/ldb_modules/rootdse.c           |   16 +-
 source4/dsdb/samdb/ldb_modules/samba_dsdb.c        |    4 +-
 .../dsdb/samdb/ldb_modules/tests/test_audit_log.c  | 2243 ++++++++++++++++++++
 .../dsdb/samdb/ldb_modules/tests/test_audit_util.c | 1260 +++++++++++
 .../samdb/ldb_modules/tests/test_group_audit.c     |  732 +++++++
 .../ldb_modules/tests/test_group_audit.valgrind    |   19 +
 source4/dsdb/samdb/ldb_modules/util.c              |    8 +-
 source4/dsdb/samdb/ldb_modules/wscript_build       |   46 +-
 .../dsdb/samdb/ldb_modules/wscript_build_server    |   32 +
 source4/rpc_server/common/server_info.c            |    3 +-
 source4/selftest/tests.py                          |   16 +
 29 files changed, 9408 insertions(+), 26 deletions(-)
 create mode 100644 python/samba/tests/audit_log_base.py
 create mode 100644 python/samba/tests/audit_log_dsdb.py
 create mode 100644 python/samba/tests/audit_log_pass_change.py
 create mode 100644 python/samba/tests/group_audit.py
 create mode 100644 source4/dsdb/samdb/ldb_modules/audit_log.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/audit_util.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/group_audit.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c
 create mode 100644 source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind


Changeset truncated at 500 lines:

diff --git a/lib/ldb-samba/ldb_ildap.c b/lib/ldb-samba/ldb_ildap.c
index 0cdf738..1b9a25e 100644
--- a/lib/ldb-samba/ldb_ildap.c
+++ b/lib/ldb-samba/ldb_ildap.c
@@ -48,6 +48,7 @@
 #include "libcli/ldap/ldap_client.h"
 #include "auth/auth.h"
 #include "auth/credentials/credentials.h"
+#include "dsdb/common/util.h"
 
 struct ildb_private {
 	struct ldap_connection *ldap;
@@ -833,7 +834,9 @@ static int ildb_connect(struct ldb_context *ldb, const char *url,
 	/* caller can optionally setup credentials using the opaque token 'credentials' */
 	creds = talloc_get_type(ldb_get_opaque(ldb, "credentials"), struct cli_credentials);
 	if (creds == NULL) {
-		struct auth_session_info *session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"), struct auth_session_info);
+		struct auth_session_info *session_info = talloc_get_type(
+			ldb_get_opaque(ldb, DSDB_SESSION_INFO),
+			struct auth_session_info);
 		if (session_info) {
 			creds = session_info->credentials;
 		}
diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c
index aa86979..063a5d3 100644
--- a/lib/ldb-samba/ldb_matching_rules.c
+++ b/lib/ldb-samba/ldb_matching_rules.c
@@ -361,7 +361,7 @@ static int dsdb_match_for_expunge(struct ldb_context *ldb,
 	}
 
 	session_info
-		= talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
+		= talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO),
 				  struct auth_session_info);
 	if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
 		return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
diff --git a/lib/ldb-samba/ldb_wrap.c b/lib/ldb-samba/ldb_wrap.c
index 34148a1..6c2c707 100644
--- a/lib/ldb-samba/ldb_wrap.c
+++ b/lib/ldb-samba/ldb_wrap.c
@@ -33,6 +33,7 @@
 #include "lib/ldb-samba/ldif_handlers.h"
 #include "ldb_wrap.h"
 #include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
 #include "param/param.h"
 #include "../lib/util/dlinklist.h"
 #include "lib/util/util_paths.h"
@@ -146,7 +147,7 @@ char *wrap_casefold(void *context, void *mem_ctx, const char *s, size_t n)
 	ldb_set_utf8_fns(ldb, NULL, wrap_casefold);
 
 	if (session_info) {
-		if (ldb_set_opaque(ldb, "sessionInfo", session_info)) {
+		if (ldb_set_opaque(ldb, DSDB_SESSION_INFO, session_info)) {
 			talloc_free(ldb);
 			return NULL;
 		}
diff --git a/lib/ldb-samba/pyldb.c b/lib/ldb-samba/pyldb.c
index dfcb551..57c5397 100644
--- a/lib/ldb-samba/pyldb.c
+++ b/lib/ldb-samba/pyldb.c
@@ -29,6 +29,7 @@
 #include "ldb_wrap.h"
 #include "lib/ldb-samba/ldif_handlers.h"
 #include "auth/pyauth.h"
+#include "source4/dsdb/common/util.h"
 
 
 static PyObject *pyldb_module;
@@ -194,7 +195,7 @@ static PyObject *py_ldb_set_session_info(PyObject *self, PyObject *args)
 
 	info = PyAuthSession_AsSession(py_session_info);
 
-	ldb_set_opaque(ldb, "sessionInfo", info);
+	ldb_set_opaque(ldb, DSDB_SESSION_INFO, info);
 
 	Py_RETURN_NONE;
 }
diff --git a/lib/ldb-samba/samba_extensions.c b/lib/ldb-samba/samba_extensions.c
index 28c820e..45b01e1 100644
--- a/lib/ldb-samba/samba_extensions.c
+++ b/lib/ldb-samba/samba_extensions.c
@@ -29,6 +29,7 @@
 #include "auth/auth.h"
 #include "param/param.h"
 #include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
 #include "ldb_wrap.h"
 #include "popt.h"
 
@@ -84,7 +85,11 @@ static int extensions_hook(struct ldb_context *ldb, enum ldb_module_hook_type t)
 		}
 		gensec_init();
 
-		if (ldb_set_opaque(ldb, "sessionInfo", system_session(cmdline_lp_ctx))) {
+		if (ldb_set_opaque(
+			ldb,
+			DSDB_SESSION_INFO,
+			system_session(cmdline_lp_ctx))) {
+
 			return ldb_operr(ldb);
 		}
 		if (ldb_set_opaque(ldb, "credentials",
diff --git a/python/samba/tests/audit_log_base.py b/python/samba/tests/audit_log_base.py
new file mode 100644
index 0000000..fa51911
--- /dev/null
+++ b/python/samba/tests/audit_log_base.py
@@ -0,0 +1,177 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett <abartlet at samba.org> 2017
+#
+# 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/>.
+#
+
+from __future__ import print_function
+"""Tests for DSDB audit logging.
+"""
+
+import samba.tests
+from samba.messaging import Messaging
+from samba.dcerpc.messaging import MSG_AUTH_LOG, AUTH_EVENT_NAME
+import time
+import json
+import os
+import re
+
+def getAudit(message):
+    if "type" not in message:
+        return None
+
+    type = message["type"]
+    audit = message[type]
+    return audit
+
+class AuditLogTestBase(samba.tests.TestCase):
+
+
+    def setUp(self):
+        super(AuditLogTestBase, self).setUp()
+        lp_ctx = self.get_loadparm()
+        self.msg_ctx = Messaging((1,), lp_ctx=lp_ctx)
+        self.msg_ctx.irpc_add_name(self.event_type)
+
+        #
+        # Check the remote address of a message against the one beimg used
+        # for the tests.
+        #
+        def isRemote(message):
+            audit = getAudit(message)
+            if audit is None:
+                return false
+
+            remote = audit["remoteAddress"]
+            if remote is None:
+                return False
+
+            try:
+                addr = remote.split(":")
+                return addr[1] == self.remoteAddress
+            except IndexError:
+                return False
+
+        def messageHandler(context, msgType, src, message):
+            # This does not look like sub unit output and it
+            # makes these tests much easier to debug.
+            print(message)
+            jsonMsg = json.loads(message)
+            if ((jsonMsg["type"] == "passwordChange" or
+                jsonMsg["type"] == "dsdbChange" or
+                jsonMsg["type"] == "groupChange") and
+                    isRemote(jsonMsg)):
+                context["messages"].append(jsonMsg)
+            elif jsonMsg["type"] == "dsdbTransaction":
+                context["txnMessage"] = jsonMsg
+
+        self.context = {"messages": [], "txnMessage": ""}
+        self.msg_handler_and_context = (messageHandler, self.context)
+        self.msg_ctx.register(self.msg_handler_and_context,
+                              msg_type=self.message_type)
+
+        self.msg_ctx.irpc_add_name(AUTH_EVENT_NAME)
+
+        def authHandler(context, msgType, src, message):
+            jsonMsg = json.loads(message)
+            if jsonMsg["type"] == "Authorization" and isRemote(jsonMsg):
+                # This does not look like sub unit output and it
+                # makes these tests much easier to debug.
+                print(message)
+                context["sessionId"] = jsonMsg["Authorization"]["sessionId"]
+                context["serviceDescription"] =\
+                    jsonMsg["Authorization"]["serviceDescription"]
+
+        self.auth_context = {"sessionId": "", "serviceDescription": ""}
+        self.auth_handler_and_context = (authHandler, self.auth_context)
+        self.msg_ctx.register(self.auth_handler_and_context,
+                              msg_type=MSG_AUTH_LOG)
+
+        self.discardMessages()
+
+        self.server = os.environ["SERVER"]
+        self.connection = None
+
+    def tearDown(self):
+        self.discardMessages()
+        self.msg_ctx.irpc_remove_name(self.event_type)
+        self.msg_ctx.irpc_remove_name(AUTH_EVENT_NAME)
+        if self.msg_handler_and_context:
+            self.msg_ctx.deregister(self.msg_handler_and_context,
+                                    msg_type=self.message_type)
+        if self.auth_handler_and_context:
+            self.msg_ctx.deregister(self.auth_handler_and_context,
+                                    msg_type=MSG_AUTH_LOG)
+
+    def haveExpected(self, expected, dn):
+        if dn is None:
+            return len(self.context["messages"]) >= expected
+        else:
+            received = 0
+            for msg in self.context["messages"]:
+                audit = getAudit(msg)
+                if audit["dn"].lower() == dn.lower():
+                    received += 1
+                    if received >= expected:
+                        return True
+            return False
+
+
+    def waitForMessages(self, number, connection=None, dn=None):
+        """Wait for all the expected messages to arrive
+        The connection is passed through to keep the connection alive
+        until all the logging messages have been received.
+        """
+
+        self.connection = connection
+
+        start_time = time.time()
+        while not self.haveExpected(number, dn):
+            self.msg_ctx.loop_once(0.1)
+            if time.time() - start_time > 1:
+                self.connection = None
+                print("Timed out")
+                return []
+
+        self.connection = None
+        if dn is None:
+            return self.context["messages"]
+
+        messages = []
+        for msg in self.context["messages"]:
+            audit = getAudit(msg)
+            if audit["dn"].lower() == dn.lower():
+                messages.append(msg)
+        return messages
+
+    # Discard any previously queued messages.
+    def discardMessages(self):
+        self.msg_ctx.loop_once(0.001)
+        while len(self.context["messages"]):
+            self.context["messages"] = []
+            self.msg_ctx.loop_once(0.001)
+
+    GUID_RE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
+
+    #
+    # Is the supplied GUID string correctly formatted
+    #
+    def is_guid(self, guid):
+        return re.match(self.GUID_RE, guid)
+
+    def get_session(self):
+        return self.auth_context["sessionId"]
+
+    def get_service_description(self):
+        return self.auth_context["serviceDescription"]
diff --git a/python/samba/tests/audit_log_dsdb.py b/python/samba/tests/audit_log_dsdb.py
new file mode 100644
index 0000000..5329af4
--- /dev/null
+++ b/python/samba/tests/audit_log_dsdb.py
@@ -0,0 +1,608 @@
+# Tests for SamDb password change audit logging.
+# Copyright (C) Andrew Bartlett <abartlet at samba.org> 2018
+#
+# 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/>.
+#
+
+from __future__ import print_function
+"""Tests for the SamDb logging of password changes.
+"""
+
+import samba.tests
+from samba.dcerpc.messaging import MSG_DSDB_LOG, DSDB_EVENT_NAME
+from samba.samdb import SamDB
+from samba.auth import system_session
+import os
+import time
+from samba.tests.audit_log_base import AuditLogTestBase
+from samba.tests import delete_force
+from samba.net import Net
+import samba
+from samba.dcerpc import security, lsa
+
+USER_NAME = "auditlogtestuser"
+USER_PASS = samba.generate_random_password(32, 32)
+SECOND_USER_NAME = "auditlogtestuser02"
+SECOND_USER_PASS = samba.generate_random_password(32, 32)
+
+
+class AuditLogDsdbTests(AuditLogTestBase):
+
+    def setUp(self):
+        self.message_type = MSG_DSDB_LOG
+        self.event_type   = DSDB_EVENT_NAME
+        super(AuditLogDsdbTests, self).setUp()
+
+        self.remoteAddress = os.environ["CLIENT_IP"]
+        self.server_ip = os.environ["SERVER_IP"]
+
+        host = "ldap://%s" % os.environ["SERVER"]
+        self.ldb = SamDB(url=host,
+                         session_info=system_session(),
+                         credentials=self.get_credentials(),
+                         lp=self.get_loadparm())
+        self.server = os.environ["SERVER"]
+
+        # Gets back the basedn
+        self.base_dn = self.ldb.domain_dn()
+
+        # Get the old "dSHeuristics" if it was set
+        dsheuristics = self.ldb.get_dsheuristics()
+
+        # Set the "dSHeuristics" to activate the correct "userPassword"
+        # behaviour
+        self.ldb.set_dsheuristics("000000001")
+
+        # Reset the "dSHeuristics" as they were before
+        self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
+
+        # Get the old "minPwdAge"
+        minPwdAge = self.ldb.get_minPwdAge()
+
+        # Set it temporarily to "0"
+        self.ldb.set_minPwdAge("0")
+        self.base_dn = self.ldb.domain_dn()
+
+        # Reset the "minPwdAge" as it was before
+        self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
+
+        # (Re)adds the test user USER_NAME with password USER_PASS
+        delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
+        delete_force(
+            self.ldb,
+            "cn=" + SECOND_USER_NAME + ",cn=users," + self.base_dn)
+        self.ldb.add({
+            "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
+            "objectclass": "user",
+            "sAMAccountName": USER_NAME,
+            "userPassword": USER_PASS
+        })
+
+    #
+    # Discard the messages from the setup code
+    #
+    def discardSetupMessages(self, dn):
+        messages = self.waitForMessages(2, dn=dn)
+        self.discardMessages()
+
+
+    def tearDown(self):
+        self.discardMessages()
+        super(AuditLogDsdbTests, self).tearDown()
+
+    def waitForTransaction(self, connection=None):
+        """Wait for a transaction message to arrive
+        The connection is passed through to keep the connection alive
+        until all the logging messages have been received.
+        """
+
+        self.connection = connection
+
+        start_time = time.time()
+        while self.context["txnMessage"] == "":
+            self.msg_ctx.loop_once(0.1)
+            if time.time() - start_time > 1:
+                self.connection = None
+                return ""
+
+        self.connection = None
+        return self.context["txnMessage"]
+
+    def test_net_change_password(self):
+
+        dn = "CN=" + USER_NAME + ",CN=Users," + self.base_dn
+        self.discardSetupMessages(dn)
+
+        creds = self.insta_creds(template=self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server)
+        password = "newPassword!!42"
+
+        net.change_password(newpassword=password.encode('utf-8'),
+                            username=USER_NAME,
+                            oldpassword=USER_PASS)
+
+        messages = self.waitForMessages(1, net, dn=dn)
+        print("Received %d messages" % len(messages))
+        self.assertEquals(1,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+        audit = messages[0]["dsdbChange"]
+        self.assertEquals("Modify", audit["operation"])
+        self.assertFalse(audit["performedAsSystem"])
+        self.assertTrue(dn.lower(), audit["dn"].lower())
+        self.assertRegexpMatches(audit["remoteAddress"],
+                                 self.remoteAddress)
+        session_id = self.get_session()
+        self.assertEquals(session_id, audit["sessionId"])
+        service_description = self.get_service_description()
+        self.assertEquals(service_description, "DCE/RPC")
+        self.assertTrue(self.is_guid(audit["transactionId"]))
+
+        attributes = audit["attributes"]
+        self.assertEquals(1, len(attributes))
+        actions = attributes["clearTextPassword"]["actions"]
+        self.assertEquals(1, len(actions))
+        self.assertTrue(actions[0]["redacted"])
+        self.assertEquals("replace", actions[0]["action"])
+
+    def test_net_set_password(self):
+
+        dn = "CN=" + USER_NAME + ",CN=Users," + self.base_dn
+        self.discardSetupMessages(dn)
+
+        creds = self.insta_creds(template=self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server)
+        password = "newPassword!!42"
+        domain = lp.get("workgroup")
+
+        net.set_password(newpassword=password.encode('utf-8'),
+                         account_name=USER_NAME,
+                         domain_name=domain)
+        messages = self.waitForMessages(1, net, dn=dn)
+        print("Received %d messages" % len(messages))
+        self.assertEquals(1,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+        audit = messages[0]["dsdbChange"]
+        self.assertEquals("Modify", audit["operation"])
+        self.assertFalse(audit["performedAsSystem"])
+        self.assertEquals(dn, audit["dn"])
+        self.assertRegexpMatches(audit["remoteAddress"],
+                                 self.remoteAddress)
+        session_id = self.get_session()
+        self.assertEquals(session_id, audit["sessionId"])
+        service_description = self.get_service_description()
+        self.assertEquals(service_description, "DCE/RPC")
+        self.assertTrue(self.is_guid(audit["transactionId"]))
+
+        attributes = audit["attributes"]
+        self.assertEquals(1, len(attributes))
+        actions = attributes["clearTextPassword"]["actions"]
+        self.assertEquals(1, len(actions))
+        self.assertTrue(actions[0]["redacted"])
+        self.assertEquals("replace", actions[0]["action"])
+
+    def test_ldap_change_password(self):
+
+        dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
+        self.discardSetupMessages(dn)
+
+        new_password = samba.generate_random_password(32, 32)
+        dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
+        self.ldb.modify_ldif(


-- 
Samba Shared Repository



More information about the samba-cvs mailing list