diff options
Diffstat (limited to 'source4/dsdb/tests/python/passwords.py')
-rwxr-xr-x | source4/dsdb/tests/python/passwords.py | 1454 |
1 files changed, 1454 insertions, 0 deletions
diff --git a/source4/dsdb/tests/python/passwords.py b/source4/dsdb/tests/python/passwords.py new file mode 100755 index 0000000..4e2182a --- /dev/null +++ b/source4/dsdb/tests/python/passwords.py @@ -0,0 +1,1454 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# This tests the password changes over LDAP for AD implementations +# +# Copyright Matthias Dieter Wallnoefer 2010 +# +# Notice: This tests will also work against Windows Server if the connection is +# secured enough (SASL with a minimum of 128 Bit encryption) - consider +# MS-ADTS 3.1.1.3.1.5 + +import optparse +import sys +import base64 +import time +import os + +sys.path.insert(0, "bin/python") + +from samba.tests.subunitrun import SubunitOptions, TestProgram +from samba.tests.password_test import PasswordTestCase + +import samba.getopt as options + +from samba.auth import system_session +from samba.credentials import Credentials +from samba.dcerpc import drsblobs, misc, security +from samba.drs_utils import drsuapi_connect +from samba.ndr import ndr_unpack +from ldb import SCOPE_BASE, LdbError +from ldb import ERR_ATTRIBUTE_OR_VALUE_EXISTS +from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS +from ldb import ERR_NO_SUCH_ATTRIBUTE +from ldb import ERR_CONSTRAINT_VIOLATION +from ldb import ERR_INVALID_CREDENTIALS +from ldb import Message, MessageElement, Dn +from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from samba import gensec, net, werror +from samba.samdb import SamDB +from samba.tests import delete_force + +parser = optparse.OptionParser("passwords.py [options] <host>") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) + +# Force an encrypted connection +creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + +# +# Tests start here +# + + +class PasswordTests(PasswordTestCase): + + def setUp(self): + super(PasswordTests, self).setUp() + self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) + + # permit password changes during this test + self.allow_password_changes() + + self.base_dn = self.ldb.domain_dn() + + # (Re)adds the test user "testuser" with no password atm + delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn) + self.ldb.add({ + "dn": "cn=testuser,cn=users," + self.base_dn, + "objectclass": "user", + "sAMAccountName": "testuser"}) + + # Tests a password change when we don't have any password yet with a + # wrong old password + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: noPassword +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e: + (num, msg) = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + # Windows (2008 at least) seems to have some small bug here: it + # returns "0000056A" on longer (always wrong) previous passwords. + self.assertTrue('00000056' in msg) + + # Sets the initial user password with a "special" password change + # I think that this internally is a password set operation and it can + # only be performed by someone which has password set privileges on the + # account (at least in s4 we do handle it like that). + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +add: userPassword +userPassword: thatsAcomplPASS1 +""") + + # But in the other way around this special syntax doesn't work + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +""") + self.fail() + except LdbError as e1: + (num, _) = e1.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + # Enables the user account + self.ldb.enable_account("(sAMAccountName=testuser)") + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for information like the domain, the realm + # and the workstation. + creds2 = Credentials() + creds2.set_username("testuser") + creds2.set_password("thatsAcomplPASS1") + creds2.set_domain(creds.get_domain()) + creds2.set_realm(creds.get_realm()) + creds2.set_workstation(creds.get_workstation()) + creds2.set_gensec_features(creds2.get_gensec_features() + | gensec.FEATURE_SEAL) + self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp) + self.creds = creds2 + + def test_unicodePwd_hash_set(self): + """Performs a password hash set operation on 'unicodePwd' which should be prevented""" + # Notice: Direct hash password sets should never work + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, + "unicodePwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e2: + (num, _) = e2.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + def test_unicodePwd_hash_change(self): + """Performs a password hash change operation on 'unicodePwd' which should be prevented""" + # Notice: Direct hash password changes should never work + + # Hash password changes should never work + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +unicodePwd: XXXXXXXXXXXXXXXX +add: unicodePwd +unicodePwd: YYYYYYYYYYYYYYYY +""") + self.fail() + except LdbError as e3: + (num, _) = e3.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + def test_unicodePwd_clear_set(self): + """Performs a password cleartext set operation on 'unicodePwd'""" + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'), + FLAG_MOD_REPLACE, "unicodePwd") + self.ldb.modify(m) + + def test_unicodePwd_clear_change(self): + """Performs a password cleartext change operation on 'unicodePwd'""" + + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')).decode('utf8') + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ +""") + + # Wrong old password + 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')).decode('utf8') + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')).decode('utf8') + """ +""") + self.fail() + except LdbError as e4: + (num, msg) = e4.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg) + + # A change to the same password again will not work (password history) + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ +""") + self.fail() + except LdbError as e5: + (num, msg) = e5.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('0000052D' in msg) + + def test_old_password_simple_bind(self): + '''Shows that we can log in with the immediate previous password, but not any earlier passwords.''' + + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can still log in using the previous password. + self.creds.set_bind_dn(user_dn_str) + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + # Change the account password a second time. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#3', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can no longer log in using the original password. + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError as err: + HRES_SEC_E_INVALID_TOKEN = '80090308' + + num, estr = err.args + self.assertEqual(ERR_INVALID_CREDENTIALS, num) + self.assertIn(HRES_SEC_E_INVALID_TOKEN, estr) + else: + self.fail('should have failed to login with previous password!') + + def test_old_password_attempt_reuse(self): + '''Shows that we cannot reuse the original password after changing the password twice.''' + res = self.ldb.search(self.ldb.domain_dn(), scope=SCOPE_BASE, + attrs=['pwdHistoryLength']) + + history_len = int(res[0].get('pwdHistoryLength', idx=0)) + self.assertGreaterEqual(history_len, 3) + + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + first_pwd = self.creds.get_password() + previous_pwd = first_pwd + + for new_pwd in ['Password#0', 'Password#1']: + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(previous_pwd, + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(new_pwd, + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the original password is in the history by trying to + # set it as our new password. + m = Message(user_dn) + m['0'] = MessageElement(new_pwd, + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(first_pwd, + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + previous_pwd = new_pwd + + def test_old_password_rename_simple_bind(self): + '''Shows that we can log in with the previous password after renaming the account.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can still log in using the previous password. + self.creds.set_bind_dn(user_dn_str) + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Show that a simple bind can still be performed using the previous + # password. + self.creds.set_username('testuser_2') + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + def test_old_password_rename_simple_bind_2(self): + '''Shows that we can rename the account, change the password and log in with the previous password.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Change the account password, causing the new salt to be stored. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that a simple bind can still be performed using the previous + # password. + self.creds.set_bind_dn(user_dn_str) + self.creds.set_username('testuser_2') + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + def test_old_password_rename_attempt_reuse(self): + '''Shows that we cannot reuse the original password after renaming the account.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the previous password is in the history by trying to set it + # as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Show that the previous password is still in the history by trying to + # set it as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + def test_old_password_rename_attempt_reuse_2(self): + '''Shows that we cannot reuse the original password after renaming the account and changing the password.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Change the account password, causing the new salt to be stored. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the previous password is in the history by trying to set it + # as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + def test_protected_unicodePwd_clear_set(self): + """Performs a password cleartext set operation on 'unicodePwd' with the user in +the Protected Users group""" + + user_dn = f'cn=testuser,cn=users,{self.base_dn}' + + # Add the user to the Protected Users group. + + # Search for the Protected Users group. + group_dn = Dn(self.ldb, + f'<SID={self.ldb.get_domain_sid()}-' + f'{security.DOMAIN_RID_PROTECTED_USERS}>') + try: + group_res = self.ldb.search(base=group_dn, + scope=SCOPE_BASE, + attrs=['member']) + except LdbError as err: + self.fail(err) + + # Add the user to the list of members. + members = list(group_res[0].get('member', ())) + members.append(user_dn) + + m = Message(group_dn) + m['member'] = MessageElement(members, + FLAG_MOD_REPLACE, + 'member') + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, user_dn) + m['unicodePwd'] = MessageElement( + '"thatsAcomplPASS2"'.encode('utf-16-le'), + FLAG_MOD_REPLACE, 'unicodePwd') + self.ldb.modify(m) + + def test_protected_unicodePwd_clear_change(self): + """Performs a password cleartext change operation on 'unicodePwd' with the user +in the Protected Users group""" + + user_dn = f'cn=testuser,cn=users,{self.base_dn}' + + # Add the user to the Protected Users group. + + # Search for the Protected Users group. + group_dn = Dn(self.ldb, + f'<SID={self.ldb.get_domain_sid()}-' + f'{security.DOMAIN_RID_PROTECTED_USERS}>') + try: + group_res = self.ldb.search(base=group_dn, + scope=SCOPE_BASE, + attrs=['member']) + except LdbError as err: + self.fail(err) + + # Add the user to the list of members. + members = list(group_res[0].get('member', ())) + members.append(user_dn) + + m = Message(group_dn) + m['member'] = MessageElement(members, + FLAG_MOD_REPLACE, + 'member') + self.ldb.modify(m) + + self.ldb2.modify_ldif(f""" +dn: cn=testuser,cn=users,{self.base_dn} +changetype: modify +delete: unicodePwd +unicodePwd:: {base64.b64encode('"thatsAcomplPASS1"'.encode('utf-16-le')) + .decode('utf8')} +add: unicodePwd +unicodePwd:: {base64.b64encode('"thatsAcomplPASS2"'.encode('utf-16-le')) + .decode('utf8')} +""") + + def test_dBCSPwd_hash_set(self): + """Performs a password hash set operation on 'dBCSPwd' which should be prevented""" + # Notice: Direct hash password sets should never work + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, + "dBCSPwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e6: + (num, _) = e6.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + def test_dBCSPwd_hash_change(self): + """Performs a password hash change operation on 'dBCSPwd' which should be prevented""" + # Notice: Direct hash password changes should never work + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: dBCSPwd +dBCSPwd: XXXXXXXXXXXXXXXX +add: dBCSPwd +dBCSPwd: YYYYYYYYYYYYYYYY +""") + self.fail() + except LdbError as e7: + (num, _) = e7.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + def test_userPassword_clear_set(self): + """Performs a password cleartext set operation on 'userPassword'""" + # Notice: This works only against Windows if "dSHeuristics" has been set + # properly + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, + "userPassword") + self.ldb.modify(m) + + def test_userPassword_clear_change(self): + """Performs a password cleartext change operation on 'userPassword'""" + # Notice: This works only against Windows if "dSHeuristics" has been set + # properly + + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + + # Wrong old password + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS3 +add: userPassword +userPassword: thatsAcomplPASS4 +""") + self.fail() + except LdbError as e8: + (num, msg) = e8.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg) + + # A change to the same password again will not work (password history) + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS2 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e9: + (num, msg) = e9.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('0000052D' in msg) + + def test_clearTextPassword_clear_set(self): + """Performs a password cleartext set operation on 'clearTextPassword'""" + # Notice: This never works against Windows - only supported by us + + try: + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'), + FLAG_MOD_REPLACE, "clearTextPassword") + self.ldb.modify(m) + # this passes against s4 + except LdbError as e10: + (num, msg) = e10.args + # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it + if num != ERR_NO_SUCH_ATTRIBUTE: + raise LdbError(num, msg) + + def test_clearTextPassword_clear_change(self): + """Performs a password cleartext change operation on 'clearTextPassword'""" + # Notice: This never works against Windows - only supported by us + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')).decode('utf8') + """ +add: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ +""") + # this passes against s4 + except LdbError as e11: + (num, msg) = e11.args + # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it + if num != ERR_NO_SUCH_ATTRIBUTE: + raise LdbError(num, msg) + + # Wrong old password + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')).decode('utf8') + """ +add: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')).decode('utf8') + """ +""") + self.fail() + except LdbError as e12: + (num, msg) = e12.args + # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it + if num != ERR_NO_SUCH_ATTRIBUTE: + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg) + + # A change to the same password again will not work (password history) + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ +add: clearTextPassword +clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ +""") + self.fail() + except LdbError as e13: + (num, msg) = e13.args + # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it + if num != ERR_NO_SUCH_ATTRIBUTE: + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('0000052D' in msg) + + def test_failures(self): + """Performs some failure testing""" + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +""") + self.fail() + except LdbError as e14: + (num, _) = e14.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +""") + self.fail() + except LdbError as e15: + (num, _) = e15.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +""") + self.fail() + except LdbError as e16: + (num, _) = e16.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +""") + self.fail() + except LdbError as e17: + (num, _) = e17.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +add: userPassword +userPassword: thatsAcomplPASS1 +""") + self.fail() + except LdbError as e18: + (num, _) = e18.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +add: userPassword +userPassword: thatsAcomplPASS1 +""") + self.fail() + except LdbError as e19: + (num, _) = e19.args + self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e20: + (num, _) = e20.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e21: + (num, _) = e21.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e22: + (num, _) = e22.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e23: + (num, _) = e23.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e24: + (num, _) = e24.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e25: + (num, _) = e25.args + self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e26: + (num, _) = e26.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError as e27: + (num, _) = e27.args + self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +replace: userPassword +userPassword: thatsAcomplPASS3 +""") + self.fail() + except LdbError as e28: + (num, _) = e28.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS1 +add: userPassword +userPassword: thatsAcomplPASS2 +replace: userPassword +userPassword: thatsAcomplPASS3 +""") + self.fail() + except LdbError as e29: + (num, _) = e29.args + self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + + # Reverse order does work + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +add: userPassword +userPassword: thatsAcomplPASS2 +delete: userPassword +userPassword: thatsAcomplPASS1 +""") + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS2 +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """ +""") + # this passes against s4 + except LdbError as e30: + (num, _) = e30.args + self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) + + 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')).decode('utf8') + """ +add: userPassword +userPassword: thatsAcomplPASS4 +""") + # this passes against s4 + except LdbError as e31: + (num, _) = e31.args + self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE) + + # Several password changes at once are allowed + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +replace: userPassword +userPassword: thatsAcomplPASS1 +userPassword: thatsAcomplPASS2 +""") + + # Several password changes at once are allowed + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +replace: userPassword +userPassword: thatsAcomplPASS1 +userPassword: thatsAcomplPASS2 +replace: userPassword +userPassword: thatsAcomplPASS3 +replace: userPassword +userPassword: thatsAcomplPASS4 +""") + + # This surprisingly should work + delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) + self.ldb.add({ + "dn": "cn=testuser2,cn=users," + self.base_dn, + "objectclass": "user", + "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"]}) + + # This surprisingly should work + delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) + self.ldb.add({ + "dn": "cn=testuser2,cn=users," + self.base_dn, + "objectclass": "user", + "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, + "objectclass": "user", + "unicodePwd": []}) + self.fail() + except LdbError as e32: + (num, _) = e32.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.add({ + "dn": "cn=testuser2,cn=users," + self.base_dn, + "objectclass": "user", + "dBCSPwd": []}) + self.fail() + except LdbError as e33: + (num, _) = e33.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.add({ + "dn": "cn=testuser2,cn=users," + self.base_dn, + "objectclass": "user", + "userPassword": []}) + self.fail() + except LdbError as e34: + (num, _) = e34.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + try: + self.ldb.add({ + "dn": "cn=testuser2,cn=users," + self.base_dn, + "objectclass": "user", + "clearTextPassword": []}) + self.fail() + except LdbError as e35: + (num, _) = e35.args + self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or + num == ERR_NO_SUCH_ATTRIBUTE) # for Windows + + delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e36: + (num, _) = e36.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e37: + (num, _) = e37.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e38: + (num, _) = e38.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e39: + (num, _) = e39.args + self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or + num == ERR_NO_SUCH_ATTRIBUTE) # for Windows + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e40: + (num, _) = e40.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e41: + (num, _) = e41.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e42: + (num, _) = e42.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e43: + (num, _) = e43.args + self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or + num == ERR_NO_SUCH_ATTRIBUTE) # for Windows + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e44: + (num, _) = e44.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e45: + (num, _) = e45.args + self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + + 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 as e46: + (num, _) = e46.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword") + try: + self.ldb.modify(m) + self.fail() + except LdbError as e47: + (num, _) = e47.args + self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or + num == ERR_NO_SUCH_ATTRIBUTE) # for Windows + + def test_plain_userPassword(self): + print("Performs testing about the standard 'userPassword' behaviour") + + # Delete the "dSHeuristics" + self.ldb.set_dsheuristics(None) + + time.sleep(1) # This switching time is strictly needed! + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD, + "userPassword") + self.ldb.modify(m) + + res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + self.assertTrue(len(res) == 1) + self.assertTrue("userPassword" in res[0]) + self.assertEqual(str(res[0]["userPassword"][0]), "myPassword") + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE, + "userPassword") + self.ldb.modify(m) + + res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + self.assertTrue(len(res) == 1) + self.assertTrue("userPassword" in res[0]) + self.assertEqual(str(res[0]["userPassword"][0]), "myPassword2") + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, + "userPassword") + self.ldb.modify(m) + + res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes + self.ldb.set_dsheuristics("000000000") + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE, + "userPassword") + self.ldb.modify(m) + + res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + self.assertTrue(len(res) == 1) + self.assertTrue("userPassword" in res[0]) + self.assertEqual(str(res[0]["userPassword"][0]), "myPassword3") + + # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes + self.ldb.set_dsheuristics("000000002") + + m = Message() + m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE, + "userPassword") + self.ldb.modify(m) + + res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + self.assertTrue(len(res) == 1) + self.assertTrue("userPassword" in res[0]) + self.assertEqual(str(res[0]["userPassword"][0]), "myPassword4") + + # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) + self.ldb.set_dsheuristics("000000001") + + def test_modify_dsheuristics_userPassword(self): + print("Performs testing about reading userPassword between dsHeuristic modifies") + + # Make sure userPassword cannot be read + self.ldb.set_dsheuristics("000000000") + + # Open a new connection (with dsHeuristic=000000000) + ldb1 = SamDB(url=host, session_info=system_session(lp), + credentials=creds, lp=lp) + + # Set userPassword to be read + # This setting only affects newer connections (ldb2) + ldb1.set_dsheuristics("000000001") + time.sleep(1) + + m = Message() + m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE, + "userPassword") + ldb1.modify(m) + + res = ldb1.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # userPassword cannot be read, it wasn't set, instead the + # password was + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + # Open another new connection (with dsHeuristic=000000001) + ldb2 = SamDB(url=host, session_info=system_session(lp), + credentials=creds, lp=lp) + + res = ldb2.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # Check on the new connection that userPassword was not stored + # from ldb1 or is not readable + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + # Set userPassword to be readable + # This setting does not affect this connection + ldb2.set_dsheuristics("000000000") + time.sleep(1) + + res = ldb2.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # Check that userPassword was not stored from ldb1 + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + m = Message() + m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn) + m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, + "userPassword") + ldb2.modify(m) + + res = ldb2.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # Check despite setting it with userPassword support disabled + # on this connection it should still not be readable + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + # Only password from ldb1 is the user's password + creds2 = Credentials() + creds2.set_username("testuser") + creds2.set_password("thatsAcomplPASS1") + creds2.set_domain(creds.get_domain()) + creds2.set_realm(creds.get_realm()) + creds2.set_workstation(creds.get_workstation()) + creds2.set_gensec_features(creds2.get_gensec_features() + | gensec.FEATURE_SEAL) + + try: + SamDB(url=host, credentials=creds2, lp=lp) + except: + self.fail("testuser used the wrong password") + + ldb3 = SamDB(url=host, session_info=system_session(lp), + credentials=creds, lp=lp) + + # Check that userPassword was stored from ldb2 + res = ldb3.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # userPassword can be read + self.assertTrue(len(res) == 1) + self.assertTrue("userPassword" in res[0]) + self.assertEqual(str(res[0]["userPassword"][0]), "thatsAcomplPASS2") + + # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) + self.ldb.set_dsheuristics("000000001") + + ldb4 = SamDB(url=host, session_info=system_session(lp), + credentials=creds, lp=lp) + + # Check that userPassword that was stored from ldb2 + res = ldb4.search("cn=testuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["userPassword"]) + + # userPassword can be not be read + self.assertTrue(len(res) == 1) + self.assertFalse("userPassword" in res[0]) + + def test_zero_length(self): + # Get the old "minPwdLength" + minPwdLength = self.ldb.get_minPwdLength() + # Set it temporarily to "0" + self.ldb.set_minPwdLength("0") + + # Get the old "pwdProperties" + pwdProperties = self.ldb.get_pwdProperties() + # Set them temporarily to "0" (to deactivate eventually the complexity) + self.ldb.set_pwdProperties("0") + + self.ldb.setpassword("(sAMAccountName=testuser)", "") + + # Reset the "pwdProperties" as they were before + self.ldb.set_pwdProperties(pwdProperties) + + # Reset the "minPwdLength" as it was before + self.ldb.set_minPwdLength(minPwdLength) + + def test_pw_change_delete_no_value_userPassword(self): + """Test password change with userPassword where the delete attribute doesn't have a value""" + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: userPassword +add: userPassword +userPassword: thatsAcomplPASS1 +""") + except LdbError as e: + (num, msg) = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() + + def test_pw_change_delete_no_value_clearTextPassword(self): + """Test password change with clearTextPassword where the delete attribute doesn't have a value""" + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: clearTextPassword +add: clearTextPassword +clearTextPassword: thatsAcomplPASS2 +""") + except LdbError as e: + (num, msg) = e.args + self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or + num == ERR_NO_SUCH_ATTRIBUTE) # for Windows + else: + self.fail() + + def test_pw_change_delete_no_value_unicodePwd(self): + """Test password change with unicodePwd where the delete attribute doesn't have a value""" + + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """ +""") + except LdbError as e: + (num, msg) = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() + + def tearDown(self): + super(PasswordTests, self).tearDown() + delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn) + delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) + # Close the second LDB connection (with the user credentials) + self.ldb2 = None + + +if "://" not in host: + if os.path.isfile(host): + host_ldaps = None + host = "tdb://%s" % host + else: + host_ldaps = "ldaps://%s" % host + host = "ldap://%s" % host + +TestProgram(module=__name__, opts=subunitopts) |