mirror of
https://github.com/samba-team/samba.git
synced 2025-01-07 17:18:11 +03:00
f94f472830
This checks that the lockout settings of the PSO take effect when one is applied to a user. Import the password_settings code to create/apply a PSO with the same lockout settings that the test cases normally use. Then update the global settings so that the default lockout settings are wildly different (i.e. so the test fails if the default lockout settings get used instead of the PSO's). As the password-lockout tests are quite slow, I've selected test cases that should provide sufficient PSO coverage (rather than repeat every single password-lockout test case in its entirety). Reviewed-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Garming Sam <garming@catalyst.net.nz> Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
1286 lines
55 KiB
Python
Executable File
1286 lines
55 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- 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
|
|
#
|
|
|
|
from __future__ import print_function
|
|
import optparse
|
|
import sys
|
|
import base64
|
|
import time
|
|
|
|
sys.path.insert(0, "bin/python")
|
|
import samba
|
|
|
|
from samba.tests.subunitrun import TestProgram, SubunitOptions
|
|
|
|
import samba.getopt as options
|
|
|
|
from samba.auth import system_session
|
|
from samba.credentials import Credentials, 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 gensec, dsdb
|
|
from samba.samdb import SamDB
|
|
import samba.tests
|
|
from samba.tests import delete_force
|
|
from samba.dcerpc import security, samr
|
|
from samba.ndr import ndr_unpack
|
|
from samba.tests.pso import PasswordSettings
|
|
|
|
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 = host_url
|
|
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.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
|
|
username="lockout2ntlm",
|
|
userpass="thatsAcomplPASS0",
|
|
kerberos_state=DONT_USE_KERBEROS)
|
|
self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
def _reset_ldap_lockoutTime(self, res):
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + str(res[0].dn) + """
|
|
changetype: modify
|
|
replace: lockoutTime
|
|
lockoutTime: 0
|
|
""")
|
|
|
|
def _reset_ldap_userAccountControl(self, res):
|
|
self.assertTrue("userAccountControl" in res[0])
|
|
self.assertTrue("msDS-User-Account-Control-Computed" in 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 is "ldap_userAccountControl":
|
|
self._reset_ldap_userAccountControl(res)
|
|
elif method is "ldap_lockoutTime":
|
|
self._reset_ldap_lockoutTime(res)
|
|
elif method is "samr":
|
|
self._reset_samr(res)
|
|
else:
|
|
self.assertTrue(False, msg="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'
|
|
print("Performs a password cleartext change operation on 'userPassword' using Kerberos")
|
|
else:
|
|
logoncount_relation = 'equal'
|
|
lastlogon_relation = 'equal'
|
|
print("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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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])
|
|
|
|
print("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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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')) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e7:
|
|
(num, msg) = e7.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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)
|
|
|
|
self._reset_by_method(res, method)
|
|
|
|
# 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')) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
|
|
""")
|
|
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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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')
|
|
|
|
def use_pso_lockout_settings(self, creds):
|
|
# create a PSO with the lockout settings the test cases normally expect
|
|
pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
|
|
lockout_duration=2)
|
|
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 _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
|
|
initial_logoncount_relation=None):
|
|
print("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')) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e10:
|
|
(num, msg) = e10.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
|
|
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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e11:
|
|
(num, msg) = e11.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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)
|
|
|
|
print("two failed password change")
|
|
|
|
# Wrong old password
|
|
try:
|
|
other_ldb.modify_ldif("""
|
|
dn: """ + userdn + """
|
|
changetype: modify
|
|
delete: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e12:
|
|
(num, msg) = e12.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e13:
|
|
(num, msg) = e13.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e14:
|
|
(num, msg) = e14.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e15:
|
|
(num, msg) = e15.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000775' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
|
|
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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e16:
|
|
(num, msg) = e16.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e17:
|
|
(num, msg) = e17.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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) + """
|
|
add: unicodePwd
|
|
unicodePwd:: """ + base64.b64encode(new_utf16) + """
|
|
""")
|
|
self.fail()
|
|
except LdbError as e18:
|
|
(num, msg) = e18.args
|
|
self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
|
|
self.assertTrue('00000056' in msg, 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 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 _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.assertEquals(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, 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 _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)
|
|
|
|
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)
|
|
|
|
host_url = "ldap://%s" % host
|
|
|
|
TestProgram(module=__name__, opts=subunitopts)
|