From 069c0c3f7aef1589d65a8eb3d9b8bbf4a25bd0ad Mon Sep 17 00:00:00 2001 From: Adrian Cochrane Date: Fri, 12 Feb 2016 15:10:53 +1300 Subject: [PATCH 2/3] samdb: Adjust passwords test to better match Windows This patch introduces a new test for pwdLastSet behaviour and corrects related tests to match Windows behaviour. Tested against Windows Server 2008. Signed-off-by: Adrian Cochrane --- selftest/knownfail | 11 +++ source4/dsdb/tests/python/passwords.py | 136 ++++++++++++++++++++++++++++----- source4/setup/provision_users.ldif | 3 + 3 files changed, 130 insertions(+), 20 deletions(-) diff --git a/selftest/knownfail b/selftest/knownfail index 04a0621..54b3e68 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -296,3 +296,14 @@ # we can watch for set methods on. # ^samba.tests.dcerpc.integer.samba.tests.dcerpc.integer.IntegerTests.test_.*_into_uint8_list +# +# Tests which fail as they are inconsistant with Windows +# +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_empty_passwords\(.*\) +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_failure2\(.*\) +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_failure3\(.*\) +# Fails on s4 +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_failure18\(.*\) +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_failure19\(.*\) +# Fixed in next patch () +^samba4.ldap.passwords.python\(.*\).__main__.PasswordTests.test_pwdLastSet\(.*\) diff --git a/source4/dsdb/tests/python/passwords.py b/source4/dsdb/tests/python/passwords.py index fb3eee5..3da0bbb 100755 --- a/source4/dsdb/tests/python/passwords.py +++ b/source4/dsdb/tests/python/passwords.py @@ -21,6 +21,7 @@ from samba.tests.subunitrun import SubunitOptions, TestProgram import samba.getopt as options +from samba import unix2nttime, current_unix_time from samba.auth import system_session from samba.credentials import Credentials from ldb import SCOPE_BASE, LdbError @@ -30,6 +31,7 @@ from ldb import ERR_NO_SUCH_ATTRIBUTE from ldb import ERR_CONSTRAINT_VIOLATION from ldb import Message, MessageElement, Dn from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +import ldb from samba import gensec from samba.samdb import SamDB import samba.tests @@ -398,19 +400,13 @@ clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le' self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) - def test_failures(self): - """Performs some failure testing""" - - try: - self.ldb.modify_ldif(""" + def test_failure1(self): + self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 """) - self.fail() - except LdbError, (num, _): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" @@ -423,6 +419,7 @@ userPassword: thatsAcomplPASS1 except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure2(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -433,6 +430,7 @@ delete: userPassword except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure3(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -443,6 +441,7 @@ delete: userPassword except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure4(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -454,6 +453,7 @@ userPassword: thatsAcomplPASS1 except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + def test_failure5(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -465,6 +465,7 @@ userPassword: thatsAcomplPASS1 except LdbError, (num, _): self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + def test_failure6(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -479,6 +480,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure7(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -493,6 +495,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure8(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -507,6 +510,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure9(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -521,6 +525,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + def test_failure10(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -536,6 +541,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + def test_failure11(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -551,6 +557,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + def test_failure12(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -566,6 +573,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + def test_failure13(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -581,6 +589,7 @@ userPassword: thatsAcomplPASS2 except LdbError, (num, _): self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + def test_failure14(self): try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -596,6 +605,7 @@ userPassword: thatsAcomplPASS3 except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + def test_failure15(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -611,7 +621,7 @@ userPassword: thatsAcomplPASS3 except LdbError, (num, _): self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) - # Reverse order does work + def test_failure16(self): self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify @@ -621,32 +631,56 @@ delete: userPassword userPassword: thatsAcomplPASS1 """) + def test_failure17(self): + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +""") + try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify -delete: userPassword +add: userPassword userPassword: thatsAcomplPASS2 +delete: userPassword +userPassword: thatsAcomplPASS1 +""") + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + + def test_failure18(self): + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ """) - # this passes against s4 + self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) + def test_failure19(self): try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """ add: userPassword userPassword: thatsAcomplPASS4 """) - # this passes against s4 + self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_NO_SUCH_ATTRIBUTE) + def test_failure20(self): # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -656,6 +690,7 @@ userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS2 """) + def test_failure21(self): # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ @@ -669,6 +704,7 @@ replace: userPassword userPassword: thatsAcomplPASS4 """) + def test_delete_then_add(self): # This surprisingly should work delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) self.ldb.add({ @@ -684,8 +720,6 @@ userPassword: thatsAcomplPASS4 "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"] }) def test_empty_passwords(self): - print "Performs some empty passwords testing" - try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, @@ -820,11 +854,7 @@ userPassword: thatsAcomplPASS4 m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword") - try: - self.ldb.modify(m) - self.fail() - except LdbError, (num, _): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.ldb.modify(m) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) @@ -931,6 +961,72 @@ userPassword: thatsAcomplPASS4 # Reset the "minPwdLength" as it was before self.ldb.set_minPwdLength(minPwdLength) + def assertApprox(self, a, b, leniency): + self.assertTrue(b - leniency <= a <= b + leniency) + + def test_pwdLastSet(self): + self.ldb.add({ + "dn" : "cn=user," + self.base_dn, + "objectclass" : "user" + }) + + res1 = self.ldb.search("cn=user," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + self.assertEqual(res1[0]["pwdLastSet"][0], "0") + + now = unix2nttime(current_unix_time()) + self.ldb.modify(ldb.Message.from_dict(self.ldb, { + "dn" : "cn=user," + self.base_dn, + "pwdLastSet" : "-1" + })) + + res1 = self.ldb.search("cn=user," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + # Be a bit lenient to account for clock differences. + self.assertApprox(int(res1[0]["pwdLastSet"][0]), now, 200000000) + + self.ldb.modify(ldb.Message.from_dict(self.ldb, { + "dn" : "cn=user," + self.base_dn, + "pwdLastSet" : "0" + })) + + res1 = self.ldb.search("cn=user," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + self.assertEqual(res1[0]["pwdLastSet"][0], "0") + + delete_force(self.ldb, "cn=user," + self.base_dn) + + # Now using a different object class + self.ldb.add({ + "dn" : "cn=nonuser," + self.base_dn, + "objectclass" : "computer" + }) + + res1 = self.ldb.search("cn=nonuser," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + + now = unix2nttime(int(time.time())) + self.ldb.modify(ldb.Message.from_dict(self.ldb, { + "dn" : "cn=nonuser," + self.base_dn, + "pwdLastSet" : "-1" + })) + + res1 = self.ldb.search("cn=nonuser," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + + self.assertApprox(int(res1[0]["pwdLastSet"][0]), now, 200000000) + + self.ldb.modify(ldb.Message.from_dict(self.ldb, { + "dn" : "cn=nonuser," + self.base_dn, + "pwdLastSet" : "0" + })) + + res1 = self.ldb.search("cn=nonuser," + self.base_dn, scope=SCOPE_BASE) + self.assertEqual(len(res1), 1) + self.assertEqual(res1[0]["pwdLastSet"][0], "0") + + delete_force(self.ldb, "cn=nonuser," + self.base_dn) + def tearDown(self): super(PasswordTests, self).tearDown() delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn) diff --git a/source4/setup/provision_users.ldif b/source4/setup/provision_users.ldif index cf9622e..f51de63 100644 --- a/source4/setup/provision_users.ldif +++ b/source4/setup/provision_users.ldif @@ -47,6 +47,7 @@ accountExpires: 9223372036854775807 sAMAccountName: Administrator clearTextPassword:: ${ADMINPASS_B64} isCriticalSystemObject: TRUE +pwdLastSet: -1 dn: CN=Guest,CN=Users,${DOMAINDN} objectClass: user @@ -56,6 +57,7 @@ primaryGroupID: 514 objectSid: ${DOMAINSID}-501 sAMAccountName: Guest isCriticalSystemObject: TRUE +pwdLastSet: -1 dn: CN=krbtgt,CN=Users,${DOMAINDN} objectClass: top @@ -72,6 +74,7 @@ sAMAccountName: krbtgt servicePrincipalName: kadmin/changepw clearTextPassword:: ${KRBTGTPASS_B64} isCriticalSystemObject: TRUE +pwdLastSet: -1 # Add other groups -- 1.9.1