auth log tests: password change tests
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 20 Mar 2017 20:59:45 +0000 (09:59 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 29 Mar 2017 00:37:29 +0000 (02:37 +0200)
Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
python/samba/tests/auth_log_base.py
python/samba/tests/auth_log_pass_change.py [new file with mode: 0644]
selftest/knownfail
source4/selftest/tests.py

index 2b3ccfdbb4bc941d2f00cd94b80086854fe5b3de..e9ae46442594fa4c73211aa9a7bbc5e04f4f47f9 100644 (file)
@@ -49,12 +49,7 @@ class AuthLogTestBase(samba.tests.TestCase):
         self.msg_ctx.register(self.msg_handler_and_context,
                               msg_type=MSG_AUTH_LOG)
 
-        # Discard any previously queued messages.
-        self.msg_ctx.loop_once(0.001)
-        while len( self.context["messages"]):
-            self.msg_ctx.loop_once(0.001)
-        self.context["messages"] = []
-
+        self.discardMessages()
 
         self.remoteAddress = None
         self.server = os.environ["SERVER"]
@@ -100,3 +95,10 @@ class AuthLogTestBase(samba.tests.TestCase):
 
         self.connection = None
         return filter( isRemote, self.context["messages"])
+
+    # Discard any previously queued messages.
+    def discardMessages(self):
+        self.msg_ctx.loop_once(0.001)
+        while len( self.context["messages"]):
+            self.msg_ctx.loop_once(0.001)
+        self.context["messages"] = []
diff --git a/python/samba/tests/auth_log_pass_change.py b/python/samba/tests/auth_log_pass_change.py
new file mode 100644 (file)
index 0000000..3a9311a
--- /dev/null
@@ -0,0 +1,242 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett <abartlet@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/>.
+#
+
+"""Tests for the Auth and AuthZ logging of password changes.
+"""
+
+from samba import auth
+import samba.tests
+from samba.messaging import Messaging
+from samba.samdb import SamDB
+from samba.auth import system_session
+import json
+import os
+import samba.tests.auth_log_base
+from samba.tests import delete_force
+from samba.net import Net
+from samba import ntstatus
+import samba
+from subprocess import call
+
+USER_NAME = "authlogtestuser"
+USER_PASS = samba.generate_random_password(32,32)
+
+class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
+
+    def setUp(self):
+        super(AuthLogPassChangeTests, 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())
+
+        print "ldb %s" % type(self.ldb)
+        # Gets back the basedn
+        base_dn = self.ldb.domain_dn()
+        print "base_dn %s" % base_dn
+
+        # Gets back the configuration basedn
+        configuration_dn = self.ldb.get_config_basedn().get_linearized()
+
+        # 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)
+        self.ldb.add({
+             "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
+             "objectclass": "user",
+             "sAMAccountName": USER_NAME,
+             "userPassword": USER_PASS})
+
+        # discard any auth log messages for the password setup
+        self.discardMessages()
+
+    def tearDown(self):
+        super(AuthLogPassChangeTests, self).tearDown()
+
+
+    def test_admin_change_password(self):
+        def isLastExpectedMessage( msg):
+            return (msg["type"] == "Authentication" and
+                    msg["Authentication"]["status"]
+                        == "NT_STATUS_OK" and
+                    msg["Authentication"]["serviceDescription"]
+                        == "SAMR Password Change" and
+                    msg["Authentication"]["authDescription"]
+                        == "samr_ChangePasswordUser3")
+
+        creds = self.insta_creds(template = self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server_ip)
+        password = "newPassword!!42"
+
+        net.change_password(newpassword = password.encode('utf-8'),
+                            username    = USER_NAME,
+                            oldpassword = USER_PASS)
+
+
+        messages = self.waitForMessages( isLastExpectedMessage)
+        print "Received %d messages" % len(messages)
+        self.assertEquals(8,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+    def test_admin_change_password_new_password_fails_restriction(self):
+        def isLastExpectedMessage( msg):
+            return (msg["type"] == "Authentication" and
+                    msg["Authentication"]["status"]
+                        == "NT_STATUS_PASSWORD_RESTRICTION" and
+                    msg["Authentication"]["serviceDescription"]
+                        == "SAMR Password Change" and
+                    msg["Authentication"]["authDescription"]
+                        == "samr_ChangePasswordUser3")
+
+        creds = self.insta_creds(template=self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server_ip)
+        password = "newPassword"
+
+        exception_thrown = False
+        try:
+            net.change_password(newpassword = password.encode('utf-8'),
+                                oldpassword = USER_PASS,
+                                username = USER_NAME)
+        except Exception, msg:
+            exception_thrown = True
+        self.assertEquals(True, exception_thrown,
+                          "Expected exception not thrown")
+
+        messages = self.waitForMessages( isLastExpectedMessage)
+        self.assertEquals(8,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+    def test_admin_change_password_unknown_user(self):
+        def isLastExpectedMessage( msg):
+            return (msg["type"] == "Authentication" and
+                    msg["Authentication"]["status"]
+                        == "NT_STATUS_NO_SUCH_USER" and
+                    msg["Authentication"]["serviceDescription"]
+                        == "SAMR Password Change" and
+                    msg["Authentication"]["authDescription"]
+                        == "samr_ChangePasswordUser3")
+
+        creds = self.insta_creds(template=self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server_ip)
+        password = "newPassword!!42"
+
+        exception_thrown = False
+        try:
+            net.change_password(newpassword = password.encode('utf-8'),
+                                oldpassword = USER_PASS,
+                                username    = "badUser")
+        except Exception, msg:
+            exception_thrown = True
+        self.assertEquals(True, exception_thrown,
+                          "Expected exception not thrown")
+
+        messages = self.waitForMessages( isLastExpectedMessage)
+        self.assertEquals(8,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+    def test_admin_change_password_bad_original_password(self):
+        def isLastExpectedMessage( msg):
+            return (msg["type"] == "Authentication" and
+                    msg["Authentication"]["status"]
+                        == "NT_STATUS_WRONG_PASSWORD" and
+                    msg["Authentication"]["serviceDescription"]
+                        == "SAMR Password Change" and
+                    msg["Authentication"]["authDescription"]
+                        == "samr_ChangePasswordUser3")
+
+        creds = self.insta_creds(template=self.get_credentials())
+
+        lp = self.get_loadparm()
+        net = Net(creds, lp, server=self.server_ip)
+        password = "newPassword!!42"
+
+        exception_thrown = False
+        try:
+            net.change_password(newpassword = password.encode('utf-8'),
+                                oldpassword = "badPassword",
+                                username    = USER_NAME)
+        except Exception, msg:
+            exception_thrown = True
+        self.assertEquals(True, exception_thrown,
+                          "Expected exception not thrown")
+
+        messages = self.waitForMessages( isLastExpectedMessage)
+        self.assertEquals(8,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+    # net rap password changes are broken, but they trigger enough of the
+    # server side behaviour to exercise the code paths of interest.
+    # if we used the real password it would be too long and does not hash
+    # correctly, so we just check it triggers the wrong password path.
+    def test_rap_change_password(self):
+        def isLastExpectedMessage( msg):
+            return (msg["type"] == "Authentication" and
+                    msg["Authentication"]["serviceDescription"]
+                        == "SAMR Password Change" and
+                    msg["Authentication"]["status"]
+                        == "NT_STATUS_WRONG_PASSWORD" and
+                    msg["Authentication"]["authDescription"]
+                        == "OemChangePasswordUser2")
+
+        newPassword  = samba.generate_random_password(32,32)
+        username     = os.environ["USERNAME"]
+        password     = os.environ["PASSWORD"]
+        server       = os.environ["SERVER"]
+        server_param = "--server=%s" % server
+        creds        = "-U%s%%%s" % (username,password)
+        call(["bin/net", "rap", server_param,
+              "password", USER_NAME, "notMyPassword", "notGoingToBeMyPassword",
+              server, creds, "--option=client ipc max protocol=nt1"])
+
+        messages = self.waitForMessages( isLastExpectedMessage)
+        self.assertEquals(7,
+                          len(messages),
+                          "Did not receive the expected number of messages")
index b25038064c3c458811d46f06f4e0e473ec8abaad..6ccd7c99268a438d3b1d692631bb438aa342030b 100644 (file)
 ^samba3.smb2.credits.skipped_mid.*
 ^samba4.blackbox.dbcheck-links.release-4-5-0-pre1.dangling_multi_valued_dbcheck
 ^samba4.blackbox.dbcheck-links.release-4-5-0-pre1.dangling_multi_valued_check_missing
+#
+# rap password tests don't function in the ad_dc_ntvfs:local environment
+#
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_rap_change_password\(ad_dc_ntvfs:local\)
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_admin_change_password\(
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_admin_change_password_bad_original_password
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_admin_change_password_new_password_fails_restriction
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_admin_change_password_unknown_user
+^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_rap_change_password\(ad_dc:local\)
index bef08c8b7b5d89ac9b8cc02c73bd0d045658cf0e..3f7d6faf19a8d09f40ea35c1ce7b9fdab9d6578c 100755 (executable)
@@ -596,6 +596,12 @@ if have_jansson_support:
     planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.auth_log", extra_args=['-U"$USERNAME%$PASSWORD"'],
                            environ={'CLIENT_IP': '127.0.0.11',
                                     'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc:local", "samba.tests.auth_log_pass_change", extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.auth_log_pass_change", extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
     planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.auth_log_ncalrpc", extra_args=['-U"$USERNAME%$PASSWORD"'])
     planoldpythontestsuite("ad_dc:local", "samba.tests.auth_log_ncalrpc", extra_args=['-U"$USERNAME%$PASSWORD"'])
 planoldpythontestsuite("ad_dc", "samba.tests.dcerpc.dnsserver", extra_args=['-U"$USERNAME%$PASSWORD"'])