mirror of
https://github.com/samba-team/samba.git
synced 2025-01-05 09:18:06 +03:00
0e40506d21
Signed-off-by: Jule Anger <janger@samba.org> Reviewed-by: Andrew Bartlett <abartlet@samba.org> Autobuild-User(master): Jule Anger <janger@samba.org> Autobuild-Date(master): Tue Mar 12 10:54:49 UTC 2024 on atb-devel-224
1721 lines
73 KiB
Python
Executable File
1721 lines
73 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# This tests the password lockout behavior for AD implementations
|
|
#
|
|
# Copyright Matthias Dieter Wallnoefer 2010
|
|
# Copyright Andrew Bartlett 2013
|
|
# Copyright Stefan Metzmacher 2014
|
|
#
|
|
|
|
import optparse
|
|
import sys
|
|
import base64
|
|
import time
|
|
import subprocess
|
|
|
|
sys.path.insert(0, "bin/python")
|
|
import samba
|
|
|
|
from samba.tests.subunitrun import TestProgram, SubunitOptions
|
|
from samba.netcmd.main import samba_tool
|
|
|
|
import samba.getopt as options
|
|
|
|
from samba.auth import system_session
|
|
from samba.credentials import DONT_USE_KERBEROS, MUST_USE_KERBEROS
|
|
from ldb import SCOPE_BASE, LdbError
|
|
from ldb import ERR_CONSTRAINT_VIOLATION
|
|
from ldb import ERR_INVALID_CREDENTIALS
|
|
from ldb import Message, MessageElement, Dn
|
|
from ldb import FLAG_MOD_REPLACE
|
|
from samba import dsdb
|
|
from samba.samdb import SamDB
|
|
from samba.tests import delete_force
|
|
from samba.dcerpc import security, samr
|
|
from samba.tests.pso import PasswordSettings
|
|
from samba.net import Net
|
|
from samba import NTSTATUSError, ntstatus
|
|
import ctypes
|
|
|
|
parser = optparse.OptionParser("password_lockout.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()
|
|
global_creds = credopts.get_credentials(lp)
|
|
|
|
import password_lockout_base
|
|
|
|
#
|
|
# Tests start here
|
|
#
|
|
|
|
|
|
class PasswordTests(password_lockout_base.BasePasswordTestCase):
|
|
def setUp(self):
|
|
self.host = host
|
|
self.host_url = "ldap://%s" % host
|
|
self.host_url_ldaps = "ldaps://%s" % host
|
|
self.lp = lp
|
|
self.global_creds = global_creds
|
|
self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
|
|
credentials=self.global_creds, lp=self.lp)
|
|
super(PasswordTests, self).setUp()
|
|
|
|
self.lockout2krb5_creds = self.insta_creds(self.template_creds,
|
|
username="lockout2krb5",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=MUST_USE_KERBEROS)
|
|
self._readd_user(self.lockout2krb5_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
self.lockout2krb5_ldb = SamDB(url=self.host_url,
|
|
credentials=self.lockout2krb5_creds,
|
|
lp=lp)
|
|
|
|
self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
|
|
username="lockout2ntlm",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=DONT_USE_KERBEROS)
|
|
self._readd_user(self.lockout2ntlm_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
self.lockout2ntlm_ldb = SamDB(url=self.host_url,
|
|
credentials=self.lockout2ntlm_creds,
|
|
lp=lp)
|
|
|
|
|
|
def use_pso_lockout_settings(self, creds):
|
|
|
|
# create a PSO with the lockout settings the test cases normally expect
|
|
#
|
|
# Some test cases sleep() for self.account_lockout_duration
|
|
pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
|
|
lockout_duration=self.account_lockout_duration)
|
|
self.addCleanup(self.ldb.delete, pso.dn)
|
|
|
|
userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
|
|
pso.apply_to(userdn)
|
|
|
|
# update the global lockout settings to be wildly different to what
|
|
# the test cases normally expect
|
|
self.update_lockout_settings(threshold=10, duration=600,
|
|
observation_window=600)
|
|
|
|
def _reset_samr(self, res):
|
|
|
|
# Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
|
|
samr_user = self._open_samr_user(res)
|
|
acb_info = self.samr.QueryUserInfo(samr_user, 16)
|
|
acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
|
|
self.samr.SetUserInfo(samr_user, 16, acb_info)
|
|
self.samr.Close(samr_user)
|
|
|
|
|
|
class PasswordTestsWithoutSleep(PasswordTests):
|
|
def setUp(self):
|
|
# The tests in this class do not sleep, so we can have a
|
|
# longer window and not flap on slower hosts
|
|
self.account_lockout_duration = 30
|
|
self.lockout_observation_window = 30
|
|
super(PasswordTestsWithoutSleep, self).setUp()
|
|
|
|
def _reset_ldap_lockoutTime(self, res):
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + str(res[0].dn) + """
|
|
changetype: modify
|
|
replace: lockoutTime
|
|
lockoutTime: 0
|
|
""")
|
|
|
|
def _reset_samba_tool(self, res):
|
|
username = res[0]["sAMAccountName"][0]
|
|
|
|
result = samba_tool('user', 'unlock', username,
|
|
"-H%s" % self.host_url,
|
|
"-U%s%%%s" % (global_creds.get_username(),
|
|
global_creds.get_password()))
|
|
self.assertEqual(result, None)
|
|
|
|
def _reset_ldap_userAccountControl(self, res):
|
|
self.assertIn("userAccountControl", res[0])
|
|
self.assertIn("msDS-User-Account-Control-Computed", res[0])
|
|
|
|
uac = int(res[0]["userAccountControl"][0])
|
|
uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
|
|
|
|
uac |= uacc
|
|
uac = uac & ~dsdb.UF_LOCKOUT
|
|
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + str(res[0].dn) + """
|
|
changetype: modify
|
|
replace: userAccountControl
|
|
userAccountControl: %d
|
|
""" % uac)
|
|
|
|
def _reset_by_method(self, res, method):
|
|
if method == "ldap_userAccountControl":
|
|
self._reset_ldap_userAccountControl(res)
|
|
elif method == "ldap_lockoutTime":
|
|
self._reset_ldap_lockoutTime(res)
|
|
elif method == "samr":
|
|
self._reset_samr(res)
|
|
elif method == "samba-tool":
|
|
self._reset_samba_tool(res)
|
|
else:
|
|
self.fail("Invalid reset method[%s]" % method)
|
|
|
|
def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
|
|
initial_lastlogon_relation=None):
|
|
"""
|
|
Tests user lockout behaviour when we try to change the user's password
|
|
but specify an incorrect old-password. The method parameter specifies
|
|
how to reset the locked out account (e.g. by resetting lockoutTime)
|
|
"""
|
|
# Notice: This works only against Windows if "dSHeuristics" has been set
|
|
# properly
|
|
username = creds.get_username()
|
|
userpass = creds.get_password()
|
|
userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
|
|
|
|
use_kerberos = creds.get_kerberos_state()
|
|
if use_kerberos == MUST_USE_KERBEROS:
|
|
logoncount_relation = 'greater'
|
|
lastlogon_relation = 'greater'
|
|
self.debug("Performs a password cleartext change operation on 'userPassword' using Kerberos")
|
|
else:
|
|
logoncount_relation = 'equal'
|
|
lastlogon_relation = 'equal'
|
|
self.debug("Performs a password cleartext change operation on 'userPassword' using NTLMSSP")
|
|
|
|
if initial_lastlogon_relation is not None:
|
|
lastlogon_relation = initial_lastlogon_relation
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=("greater", 0),
|
|
logonCount=(logoncount_relation, 0),
|
|
lastLogon=(lastlogon_relation, 0),
|
|
lastLogonTimestamp=('greater', 0),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
logonCount = int(res[0]["logonCount"][0])
|
|
lastLogon = int(res[0]["lastLogon"][0])
|
|
lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
|
|
if lastlogon_relation == 'greater':
|
|
self.assertGreater(lastLogon, badPasswordTime)
|
|
self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
|
|
|
|
# Change password on a connection as another user
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1x
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e:
|
|
(num, msg) = e.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# Correct old password
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: """ + userpass + """
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1x
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e1:
|
|
(num, msg) = e1.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
self.debug("two failed password change")
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1x
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e2:
|
|
(num, msg) = e2.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=("greater", badPasswordTime),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
lockoutTime = int(res[0]["lockoutTime"][0])
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1x
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e3:
|
|
(num, msg) = e3.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1x
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e4:
|
|
(num, msg) = e4.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=lockoutTime,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
try:
|
|
# Correct old password
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2x
|
|
""")
|
|
self.fail()
|
|
except LdbError as e5:
|
|
(num, msg) = e5.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# Now reset the password, which does NOT change the lockout!
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
replace: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
try:
|
|
# Correct old password
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2x
|
|
""")
|
|
self.fail()
|
|
except LdbError as e6:
|
|
(num, msg) = e6.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
m = Message()
|
|
m.dn = Dn(self.ldb, userdn)
|
|
m["userAccountControl"] = MessageElement(
|
|
str(dsdb.UF_LOCKOUT),
|
|
FLAG_MOD_REPLACE, "userAccountControl")
|
|
|
|
self.ldb.modify(m)
|
|
|
|
# This shows that setting the UF_LOCKOUT flag alone makes no difference
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# This shows that setting the UF_LOCKOUT flag makes no difference
|
|
try:
|
|
# Correct old password
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e7:
|
|
(num, msg) = e7.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=lockoutTime,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
username = res[0]["sAMAccountName"][0]
|
|
cmd = ["bin/samba-tool", "user", "list", "--locked-only",
|
|
"-H%s" % self.host_url,
|
|
"-U%s%%%s" % (global_creds.get_username(),
|
|
global_creds.get_password())]
|
|
out = subprocess.check_output(cmd)
|
|
self.assertIn(username, out)
|
|
|
|
self._reset_by_method(res, method)
|
|
|
|
cmd = ["bin/samba-tool", "user", "list", "--locked-only",
|
|
"-H%s" % self.host_url,
|
|
"-U%s%%%s" % (global_creds.get_username(),
|
|
global_creds.get_password())]
|
|
out = subprocess.check_output(cmd)
|
|
self.assertNotIn(username, out)
|
|
|
|
# Here bad password counts are reset without logon success.
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=0,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# The correct password after doing the unlock
|
|
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
|
|
""")
|
|
userpass = "thatsAcomplPASS2x"
|
|
creds.set_password(userpass)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=0,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1xyz
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2XYZ
|
|
""")
|
|
self.fail()
|
|
except LdbError as e8:
|
|
(num, msg) = e8.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lockoutTime=0,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: thatsAcomplPASS1xyz
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2XYZ
|
|
""")
|
|
self.fail()
|
|
except LdbError as e9:
|
|
(num, msg) = e9.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lockoutTime=0,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
self._reset_ldap_lockoutTime(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# The following test lockout behaviour when modifying a user's password
|
|
# and specifying an invalid old password. There are variants for both
|
|
# NTLM and kerberos user authentication. As well as that, there are 3 ways
|
|
# to reset the locked out account: by clearing the lockout bit for
|
|
# userAccountControl (via LDAP), resetting it via SAMR, and by resetting
|
|
# the lockoutTime.
|
|
def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb,
|
|
"ldap_userAccountControl")
|
|
|
|
def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb,
|
|
"ldap_lockoutTime")
|
|
|
|
def test_userPassword_lockout_with_clear_change_krb5_samr(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb,
|
|
"samr")
|
|
|
|
def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
"ldap_userAccountControl",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
"ldap_lockoutTime",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
"samr",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
# For PSOs, just test a selection of the above combinations
|
|
def test_pso_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
|
|
self.use_pso_lockout_settings(self.lockout1krb5_creds)
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb,
|
|
"ldap_userAccountControl")
|
|
|
|
def test_pso_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
"ldap_lockoutTime",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
def test_pso_userPassword_lockout_with_clear_change_ntlm_samr(self):
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
"samr",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
# just test "samba-tool user unlock" command once
|
|
def test_userPassword_lockout_with_clear_change_krb5_ldap_samba_tool(self):
|
|
self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb,
|
|
"samba-tool")
|
|
|
|
def test_multiple_logon_krb5(self):
|
|
self._test_multiple_logon(self.lockout1krb5_creds)
|
|
|
|
def test_multiple_logon_ntlm(self):
|
|
self._test_multiple_logon(self.lockout1ntlm_creds)
|
|
|
|
def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
|
|
"""Tests user lockout by using bad password in SAMR password_change"""
|
|
|
|
# create a connection for SAMR using another user's credentials
|
|
lp = self.get_loadparm()
|
|
net = Net(other_creds, lp, server=self.host)
|
|
|
|
# work out the initial account values for this user
|
|
username = creds.get_username()
|
|
userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=("greater", 0),
|
|
badPwdCountOnly=True)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
logonCount = int(res[0]["logonCount"][0])
|
|
lastLogon = int(res[0]["lastLogon"][0])
|
|
lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
|
|
|
|
# prove we can change the user password (using the correct password)
|
|
new_password = "thatsAcomplPASS2"
|
|
net.change_password(newpassword=new_password,
|
|
username=username,
|
|
oldpassword=creds.get_password())
|
|
creds.set_password(new_password)
|
|
|
|
# try entering 'x' many bad passwords in a row to lock the user out
|
|
new_password = "thatsAcomplPASS3"
|
|
for i in range(lockout_threshold):
|
|
badPwdCount = i + 1
|
|
try:
|
|
self.debug("Trying bad password, attempt #%u" % badPwdCount)
|
|
net.change_password(newpassword=new_password,
|
|
username=creds.get_username(),
|
|
oldpassword="bad-password")
|
|
self.fail("Invalid SAMR change_password accepted")
|
|
except NTSTATUSError as e:
|
|
enum = ctypes.c_uint32(e.args[0]).value
|
|
self.assertEqual(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
|
|
|
|
# check the status of the account is updated after each bad attempt
|
|
account_flags = 0
|
|
lockoutTime = None
|
|
if badPwdCount >= lockout_threshold:
|
|
account_flags = dsdb.UF_LOCKOUT
|
|
lockoutTime = ("greater", badPasswordTime)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=account_flags)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# the user is now locked out
|
|
lockoutTime = int(res[0]["lockoutTime"][0])
|
|
|
|
# check the user remains locked out regardless of whether they use a
|
|
# good or a bad password now
|
|
for password in (creds.get_password(), "bad-password"):
|
|
try:
|
|
self.debug("Trying password %s" % password)
|
|
net.change_password(newpassword=new_password,
|
|
username=creds.get_username(),
|
|
oldpassword=password)
|
|
self.fail("Invalid SAMR change_password accepted")
|
|
except NTSTATUSError as e:
|
|
enum = ctypes.c_uint32(e.args[0]).value
|
|
self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=lockout_threshold,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# reset the user account lockout
|
|
self._reset_samr(res)
|
|
|
|
# check bad password counts are reset
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=0,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# check we can change the user password successfully now
|
|
net.change_password(newpassword=new_password,
|
|
username=username,
|
|
oldpassword=creds.get_password())
|
|
creds.set_password(new_password)
|
|
|
|
def test_samr_change_password(self):
|
|
self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
other_creds=self.lockout2ntlm_creds)
|
|
|
|
# same as above, but use a PSO to enforce the lockout
|
|
def test_pso_samr_change_password(self):
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
other_creds=self.lockout2ntlm_creds)
|
|
|
|
def test_ntlm_lockout_protected(self):
|
|
creds = self.lockout1ntlm_creds
|
|
self.assertEqual(DONT_USE_KERBEROS, creds.get_kerberos_state())
|
|
|
|
# Work out the initial account values for this user.
|
|
username = creds.get_username()
|
|
userdn = f'cn={username},cn=users,{self.base_dn}'
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=('greater', 0),
|
|
badPwdCountOnly=True)
|
|
badPasswordTime = int(res[0]['badPasswordTime'][0])
|
|
logonCount = int(res[0]['logonCount'][0])
|
|
lastLogon = int(res[0]['lastLogon'][0])
|
|
lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0])
|
|
|
|
# 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)
|
|
|
|
orig_msg = group_res[0]
|
|
|
|
# Add the user to the list of members.
|
|
members = list(orig_msg.get('member', ()))
|
|
self.assertNotIn(userdn, members, 'account already in Protected Users')
|
|
members.append(userdn)
|
|
|
|
m = Message(group_dn)
|
|
m['member'] = MessageElement(members,
|
|
FLAG_MOD_REPLACE,
|
|
'member')
|
|
cleanup = self.ldb.msg_diff(m, orig_msg)
|
|
self.ldb.modify(m)
|
|
|
|
password = creds.get_password()
|
|
creds.set_password('wrong_password')
|
|
|
|
lockout_threshold = 5
|
|
|
|
lp = self.get_loadparm()
|
|
server = f'ldap://{self.ldb.host_dns_name()}'
|
|
|
|
for _ in range(lockout_threshold):
|
|
with self.assertRaises(LdbError) as err:
|
|
SamDB(url=server,
|
|
credentials=creds,
|
|
lp=lp)
|
|
|
|
num, _ = err.exception.args
|
|
self.assertEqual(ERR_INVALID_CREDENTIALS, num)
|
|
|
|
res = self._check_account(
|
|
userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=None,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# The user should not be locked out.
|
|
self.assertNotIn('lockoutTime', res[0],
|
|
'account unexpectedly locked out')
|
|
|
|
# Move the account out of 'Protected Users'.
|
|
self.ldb.modify(cleanup)
|
|
|
|
# The account should not be locked out.
|
|
creds.set_password(password)
|
|
|
|
try:
|
|
SamDB(url=server,
|
|
credentials=creds,
|
|
lp=lp)
|
|
except LdbError:
|
|
self.fail('account unexpectedly locked out')
|
|
|
|
def test_samr_change_password_protected(self):
|
|
"""Tests the SAMR password change method for Protected Users"""
|
|
|
|
creds = self.lockout1ntlm_creds
|
|
other_creds = self.lockout2ntlm_creds
|
|
lockout_threshold = 5
|
|
|
|
# Create a connection for SAMR using another user's credentials.
|
|
lp = self.get_loadparm()
|
|
net = Net(other_creds, lp, server=self.host)
|
|
|
|
# Work out the initial account values for this user.
|
|
username = creds.get_username()
|
|
userdn = f'cn={username},cn=users,{self.base_dn}'
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=('greater', 0),
|
|
badPwdCountOnly=True)
|
|
badPasswordTime = int(res[0]['badPasswordTime'][0])
|
|
logonCount = int(res[0]['logonCount'][0])
|
|
lastLogon = int(res[0]['lastLogon'][0])
|
|
lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0])
|
|
|
|
# prove we can change the user password (using the correct password)
|
|
new_password = 'thatsAcomplPASS1'
|
|
net.change_password(newpassword=new_password,
|
|
username=username,
|
|
oldpassword=creds.get_password())
|
|
creds.set_password(new_password)
|
|
|
|
# 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)
|
|
|
|
orig_msg = group_res[0]
|
|
|
|
# Add the user to the list of members.
|
|
members = list(orig_msg.get('member', ()))
|
|
self.assertNotIn(userdn, members, 'account already in Protected Users')
|
|
members.append(userdn)
|
|
|
|
m = Message(group_dn)
|
|
m['member'] = MessageElement(members,
|
|
FLAG_MOD_REPLACE,
|
|
'member')
|
|
self.ldb.modify(m)
|
|
|
|
# Try entering the correct password 'x' times in a row, which should
|
|
# fail, but not lock the user out.
|
|
new_password = 'thatsAcomplPASS2'
|
|
for i in range(lockout_threshold):
|
|
with self.assertRaises(
|
|
NTSTATUSError,
|
|
msg='Invalid SAMR change_password accepted') as err:
|
|
self.debug(f'Trying correct password, attempt #{i}')
|
|
net.change_password(newpassword=new_password,
|
|
username=username,
|
|
oldpassword=creds.get_password())
|
|
|
|
enum = ctypes.c_uint32(err.exception.args[0]).value
|
|
self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_RESTRICTION)
|
|
|
|
res = self._check_account(
|
|
userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=None,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# The user should not be locked out.
|
|
self.assertNotIn('lockoutTime', res[0])
|
|
|
|
# Ensure that the password can still be changed via LDAP.
|
|
self.ldb.modify_ldif(f'''
|
|
dn: {userdn}
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: {creds.get_password()}
|
|
add: userPassword
|
|
userPassword: {new_password}
|
|
''')
|
|
|
|
def test_samr_set_password_protected(self):
|
|
"""Tests the SAMR password set method for Protected Users"""
|
|
|
|
creds = self.lockout1ntlm_creds
|
|
lockout_threshold = 5
|
|
|
|
# create a connection for SAMR using another user's credentials
|
|
lp = self.get_loadparm()
|
|
net = Net(self.global_creds, lp, server=self.host)
|
|
|
|
# work out the initial account values for this user
|
|
username = creds.get_username()
|
|
userdn = f'cn={username},cn=users,{self.base_dn}'
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=('greater', 0),
|
|
badPwdCountOnly=True)
|
|
badPasswordTime = int(res[0]['badPasswordTime'][0])
|
|
logonCount = int(res[0]['logonCount'][0])
|
|
lastLogon = int(res[0]['lastLogon'][0])
|
|
lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0])
|
|
|
|
# prove we can change the user password (using the correct password)
|
|
new_password = 'thatsAcomplPASS1'
|
|
net.set_password(newpassword=new_password,
|
|
account_name=username,
|
|
domain_name=creds.get_domain())
|
|
creds.set_password(new_password)
|
|
|
|
# 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)
|
|
|
|
orig_msg = group_res[0]
|
|
|
|
# Add the user to the list of members.
|
|
members = list(orig_msg.get('member', ()))
|
|
self.assertNotIn(userdn, members, 'account already in Protected Users')
|
|
members.append(userdn)
|
|
|
|
m = Message(group_dn)
|
|
m['member'] = MessageElement(members,
|
|
FLAG_MOD_REPLACE,
|
|
'member')
|
|
self.ldb.modify(m)
|
|
|
|
# Try entering the correct password 'x' times in a row, which should
|
|
# fail, but not lock the user out.
|
|
new_password = 'thatsAcomplPASS2'
|
|
for i in range(lockout_threshold):
|
|
with self.assertRaises(
|
|
NTSTATUSError,
|
|
msg='Invalid SAMR set_password accepted') as err:
|
|
self.debug(f'Trying correct password, attempt #{i}')
|
|
net.set_password(newpassword=new_password,
|
|
account_name=username,
|
|
domain_name=creds.get_domain())
|
|
|
|
enum = ctypes.c_uint32(err.exception.args[0]).value
|
|
self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_RESTRICTION)
|
|
|
|
res = self._check_account(
|
|
userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=None,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# The user should not be locked out.
|
|
self.assertNotIn('lockoutTime', res[0])
|
|
|
|
# Ensure that the password can still be changed via LDAP.
|
|
self.ldb.modify_ldif(f'''
|
|
dn: {userdn}
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: {creds.get_password()}
|
|
add: userPassword
|
|
userPassword: {new_password}
|
|
''')
|
|
|
|
|
|
class PasswordTestsWithSleep(PasswordTests):
|
|
def setUp(self):
|
|
super(PasswordTestsWithSleep, self).setUp()
|
|
|
|
def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
|
|
initial_logoncount_relation=None):
|
|
self.debug("Performs a password cleartext change operation on 'unicodePwd'")
|
|
username = creds.get_username()
|
|
userpass = creds.get_password()
|
|
userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
|
|
if initial_logoncount_relation is not None:
|
|
logoncount_relation = initial_logoncount_relation
|
|
else:
|
|
logoncount_relation = "greater"
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=("greater", 0),
|
|
logonCount=(logoncount_relation, 0),
|
|
lastLogon=("greater", 0),
|
|
lastLogonTimestamp=("greater", 0),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
logonCount = int(res[0]["logonCount"][0])
|
|
lastLogon = int(res[0]["lastLogon"][0])
|
|
lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
|
|
self.assertGreater(lastLogonTimestamp, badPasswordTime)
|
|
self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
|
|
|
|
# Change password on a connection as another user
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e10:
|
|
(num, msg) = e10.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# Correct old password
|
|
old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
|
|
invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
|
|
userpass = "thatsAcomplPASS2"
|
|
creds.set_password(userpass)
|
|
new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
|
|
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e11:
|
|
(num, msg) = e11.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
|
|
# It doesn't create "lockoutTime" = 0 and doesn't
|
|
# reset "badPwdCount" = 0.
|
|
self._reset_samr(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
self.debug("two failed password change")
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e12:
|
|
(num, msg) = e12.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
# this is strange, why do we have lockoutTime=badPasswordTime here?
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=("greater", badPasswordTime),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
lockoutTime = int(res[0]["lockoutTime"][0])
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e13:
|
|
(num, msg) = e13.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e14:
|
|
(num, msg) = e14.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
try:
|
|
# Correct old password
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e15:
|
|
(num, msg) = e15.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000775', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
|
|
# Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
|
|
self._reset_samr(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Correct old password
|
|
old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
|
|
invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
|
|
userpass = "thatsAcomplPASS2x"
|
|
creds.set_password(userpass)
|
|
new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
|
|
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e16:
|
|
(num, msg) = e16.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e17:
|
|
(num, msg) = e17.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
|
|
# It doesn't reset "badPwdCount" = 0.
|
|
self._reset_samr(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=2,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=0,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e18:
|
|
(num, msg) = e18.args
|
|
self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3,
|
|
badPasswordTime=("greater", badPasswordTime),
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=("greater", badPasswordTime),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
lockoutTime = int(res[0]["lockoutTime"][0])
|
|
|
|
time.sleep(self.account_lockout_duration + 1)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3, effective_bad_password_count=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
lockoutTime=lockoutTime,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
|
|
# It doesn't reset "lockoutTime" = 0 and doesn't
|
|
# reset "badPwdCount" = 0.
|
|
self._reset_samr(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=3, effective_bad_password_count=0,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lockoutTime=lockoutTime,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
def test_unicodePwd_lockout_with_clear_change_krb5(self):
|
|
self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
|
|
self.lockout2krb5_ldb)
|
|
|
|
def test_unicodePwd_lockout_with_clear_change_ntlm(self):
|
|
self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
|
|
self.lockout2ntlm_ldb,
|
|
initial_logoncount_relation="equal")
|
|
|
|
def test_login_lockout_krb5(self):
|
|
self._test_login_lockout(self.lockout1krb5_creds)
|
|
|
|
def test_login_lockout_ntlm(self):
|
|
self._test_login_lockout(self.lockout1ntlm_creds)
|
|
|
|
# Repeat the login lockout tests using PSOs
|
|
def test_pso_login_lockout_krb5(self):
|
|
"""Check the PSO lockout settings get applied to the user correctly"""
|
|
self.use_pso_lockout_settings(self.lockout1krb5_creds)
|
|
self._test_login_lockout(self.lockout1krb5_creds)
|
|
|
|
def test_pso_login_lockout_ntlm(self):
|
|
"""Check the PSO lockout settings get applied to the user correctly"""
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_login_lockout(self.lockout1ntlm_creds)
|
|
|
|
def _testing_add_user(self, creds, lockOutObservationWindow=0):
|
|
username = creds.get_username()
|
|
userpass = creds.get_password()
|
|
userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
|
|
|
|
use_kerberos = creds.get_kerberos_state()
|
|
if use_kerberos == MUST_USE_KERBEROS:
|
|
logoncount_relation = 'greater'
|
|
lastlogon_relation = 'greater'
|
|
else:
|
|
logoncount_relation = 'equal'
|
|
if lockOutObservationWindow == 0:
|
|
lastlogon_relation = 'greater'
|
|
else:
|
|
lastlogon_relation = 'equal'
|
|
|
|
delete_force(self.ldb, userdn)
|
|
self.ldb.add({
|
|
"dn": userdn,
|
|
"objectclass": "user",
|
|
"sAMAccountName": username})
|
|
|
|
self.addCleanup(delete_force, self.ldb, userdn)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=0,
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
|
|
dsdb.UF_ACCOUNTDISABLE |
|
|
dsdb.UF_PASSWD_NOTREQD),
|
|
msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
|
|
|
|
# SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
|
|
# It doesn't create "lockoutTime" = 0.
|
|
self._reset_samr(res)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=0,
|
|
badPasswordTime=0,
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
|
|
dsdb.UF_ACCOUNTDISABLE |
|
|
dsdb.UF_PASSWD_NOTREQD),
|
|
msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
|
|
|
|
# Tests a password change when we don't have any password yet with a
|
|
# wrong old password
|
|
try:
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
userPassword: noPassword
|
|
add: userPassword
|
|
userPassword: thatsAcomplPASS2
|
|
""")
|
|
self.fail()
|
|
except LdbError as e19:
|
|
(num, msg) = e19.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.assertIn('00000056', msg)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=1,
|
|
badPasswordTime=("greater", 0),
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
|
|
dsdb.UF_ACCOUNTDISABLE |
|
|
dsdb.UF_PASSWD_NOTREQD),
|
|
msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
|
|
badPwdCount = int(res[0]["badPwdCount"][0])
|
|
badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
|
|
# 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: """ + userdn + """
|
|
changetype: modify
|
|
delete: userPassword
|
|
add: userPassword
|
|
userPassword: """ + userpass + """
|
|
""")
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
|
|
dsdb.UF_ACCOUNTDISABLE |
|
|
dsdb.UF_PASSWD_NOTREQD),
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
# Enables the user account
|
|
self.ldb.enable_account("(sAMAccountName=%s)" % username)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
if lockOutObservationWindow != 0:
|
|
time.sleep(lockOutObservationWindow + 1)
|
|
effective_bad_password_count = 0
|
|
else:
|
|
effective_bad_password_count = badPwdCount
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
effective_bad_password_count=effective_bad_password_count,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=0,
|
|
lastLogon=0,
|
|
lastLogonTimestamp=('absent', None),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
|
|
|
|
if lockOutObservationWindow == 0:
|
|
badPwdCount = 0
|
|
effective_bad_password_count = 0
|
|
if use_kerberos == MUST_USE_KERBEROS:
|
|
badPwdCount = 0
|
|
effective_bad_password_count = 0
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
effective_bad_password_count=effective_bad_password_count,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=(logoncount_relation, 0),
|
|
lastLogon=(lastlogon_relation, 0),
|
|
lastLogonTimestamp=('greater', badPasswordTime),
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
|
|
logonCount = int(res[0]["logonCount"][0])
|
|
lastLogon = int(res[0]["lastLogon"][0])
|
|
lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
|
|
if lastlogon_relation == 'greater':
|
|
self.assertGreater(lastLogon, badPasswordTime)
|
|
self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
|
|
|
|
res = self._check_account(userdn,
|
|
badPwdCount=badPwdCount,
|
|
effective_bad_password_count=effective_bad_password_count,
|
|
badPasswordTime=badPasswordTime,
|
|
logonCount=logonCount,
|
|
lastLogon=lastLogon,
|
|
lastLogonTimestamp=lastLogonTimestamp,
|
|
userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
msDSUserAccountControlComputed=0)
|
|
return ldb
|
|
|
|
def test_lockout_observation_window(self):
|
|
lockout3krb5_creds = self.insta_creds(self.template_creds,
|
|
username="lockout3krb5",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=MUST_USE_KERBEROS)
|
|
self._testing_add_user(lockout3krb5_creds)
|
|
|
|
lockout4krb5_creds = self.insta_creds(self.template_creds,
|
|
username="lockout4krb5",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=MUST_USE_KERBEROS)
|
|
self._testing_add_user(lockout4krb5_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
lockout3ntlm_creds = self.insta_creds(self.template_creds,
|
|
username="lockout3ntlm",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=DONT_USE_KERBEROS)
|
|
self._testing_add_user(lockout3ntlm_creds)
|
|
lockout4ntlm_creds = self.insta_creds(self.template_creds,
|
|
username="lockout4ntlm",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=DONT_USE_KERBEROS)
|
|
self._testing_add_user(lockout4ntlm_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
class PasswordTestsWithDefaults(PasswordTests):
|
|
def setUp(self):
|
|
# The tests in this class do not sleep, so we can use the default
|
|
# timeout windows here
|
|
self.account_lockout_duration = 30 * 60
|
|
self.lockout_observation_window = 30 * 60
|
|
super(PasswordTestsWithDefaults, self).setUp()
|
|
|
|
# sanity-check that user lockout works with the default settings (we just
|
|
# check the user is locked out - we don't wait for the lockout to expire)
|
|
def test_login_lockout_krb5(self):
|
|
self._test_login_lockout(self.lockout1krb5_creds,
|
|
wait_lockout_duration=False)
|
|
|
|
def test_login_lockout_ntlm(self):
|
|
self._test_login_lockout(self.lockout1ntlm_creds,
|
|
wait_lockout_duration=False)
|
|
|
|
# Repeat the login lockout tests using PSOs
|
|
def test_pso_login_lockout_krb5(self):
|
|
"""Check the PSO lockout settings get applied to the user correctly"""
|
|
self.use_pso_lockout_settings(self.lockout1krb5_creds)
|
|
self._test_login_lockout(self.lockout1krb5_creds,
|
|
wait_lockout_duration=False)
|
|
|
|
def test_pso_login_lockout_ntlm(self):
|
|
"""Check the PSO lockout settings get applied to the user correctly"""
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_login_lockout(self.lockout1ntlm_creds,
|
|
wait_lockout_duration=False)
|
|
|
|
TestProgram(module=__name__, opts=subunitopts)
|