mirror of
https://github.com/samba-team/samba.git
synced 2025-01-14 19:24:43 +03:00
d0a9e19114
Any test that wants to change a password has to set the dSHeuristics and minPwdAge first in order for the password change to work. The code that does this is duplicated in several tests. This patch splits it out into a static method so that the code can be reused rather than duplicated. Reviewed-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Garming Sam <garming@catalyst.net.nz> Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
306 lines
12 KiB
Python
306 lines
12 KiB
Python
# Unix SMB/CIFS implementation.
|
|
# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from __future__ import print_function
|
|
"""Tests for the Auth and AuthZ logging of password changes.
|
|
"""
|
|
|
|
import samba.tests
|
|
from samba.samdb import SamDB
|
|
from samba.auth import system_session
|
|
import os
|
|
import samba.tests.auth_log_base
|
|
from samba.tests import delete_force
|
|
from samba.net import Net
|
|
import samba
|
|
from subprocess import call
|
|
from ldb import LdbError
|
|
from samba.tests.password_test import PasswordCommon
|
|
|
|
USER_NAME = "authlogtestuser"
|
|
USER_PASS = samba.generate_random_password(32, 32)
|
|
|
|
|
|
class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
|
|
|
|
def setUp(self):
|
|
super(AuthLogPassChangeTests, self).setUp()
|
|
|
|
self.remoteAddress = os.environ["CLIENT_IP"]
|
|
self.server_ip = os.environ["SERVER_IP"]
|
|
|
|
host = "ldap://%s" % os.environ["SERVER"]
|
|
self.ldb = SamDB(url=host,
|
|
session_info=system_session(),
|
|
credentials=self.get_credentials(),
|
|
lp=self.get_loadparm())
|
|
|
|
print("ldb %s" % type(self.ldb))
|
|
# Gets back the basedn
|
|
base_dn = self.ldb.domain_dn()
|
|
print("base_dn %s" % base_dn)
|
|
|
|
# permit password changes during this test
|
|
PasswordCommon.allow_password_changes(self, self.ldb)
|
|
|
|
self.base_dn = self.ldb.domain_dn()
|
|
|
|
# (Re)adds the test user USER_NAME with password USER_PASS
|
|
delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
|
|
self.ldb.add({
|
|
"dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
|
|
"objectclass": "user",
|
|
"sAMAccountName": USER_NAME,
|
|
"userPassword": USER_PASS
|
|
})
|
|
|
|
# discard any auth log messages for the password setup
|
|
self.discardMessages()
|
|
|
|
def tearDown(self):
|
|
super(AuthLogPassChangeTests, self).tearDown()
|
|
|
|
def test_admin_change_password(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] == "NT_STATUS_OK") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"SAMR Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"samr_ChangePasswordUser3"))
|
|
|
|
creds = self.insta_creds(template=self.get_credentials())
|
|
|
|
lp = self.get_loadparm()
|
|
net = Net(creds, lp, server=self.server_ip)
|
|
password = "newPassword!!42"
|
|
|
|
net.change_password(newpassword=password.encode('utf-8'),
|
|
username=USER_NAME,
|
|
oldpassword=USER_PASS)
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
print("Received %d messages" % len(messages))
|
|
self.assertEquals(8,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
def test_admin_change_password_new_password_fails_restriction(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] ==
|
|
"NT_STATUS_PASSWORD_RESTRICTION") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"SAMR Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"samr_ChangePasswordUser3"))
|
|
|
|
creds = self.insta_creds(template=self.get_credentials())
|
|
|
|
lp = self.get_loadparm()
|
|
net = Net(creds, lp, server=self.server_ip)
|
|
password = "newPassword"
|
|
|
|
exception_thrown = False
|
|
try:
|
|
net.change_password(newpassword=password.encode('utf-8'),
|
|
oldpassword=USER_PASS,
|
|
username=USER_NAME)
|
|
except Exception:
|
|
exception_thrown = True
|
|
self.assertEquals(True, exception_thrown,
|
|
"Expected exception not thrown")
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
self.assertEquals(8,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
def test_admin_change_password_unknown_user(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] ==
|
|
"NT_STATUS_NO_SUCH_USER") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"SAMR Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"samr_ChangePasswordUser3"))
|
|
|
|
creds = self.insta_creds(template=self.get_credentials())
|
|
|
|
lp = self.get_loadparm()
|
|
net = Net(creds, lp, server=self.server_ip)
|
|
password = "newPassword!!42"
|
|
|
|
exception_thrown = False
|
|
try:
|
|
net.change_password(newpassword=password.encode('utf-8'),
|
|
oldpassword=USER_PASS,
|
|
username="badUser")
|
|
except Exception:
|
|
exception_thrown = True
|
|
self.assertEquals(True, exception_thrown,
|
|
"Expected exception not thrown")
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
self.assertEquals(8,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
def test_admin_change_password_bad_original_password(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] ==
|
|
"NT_STATUS_WRONG_PASSWORD") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"SAMR Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"samr_ChangePasswordUser3"))
|
|
|
|
creds = self.insta_creds(template=self.get_credentials())
|
|
|
|
lp = self.get_loadparm()
|
|
net = Net(creds, lp, server=self.server_ip)
|
|
password = "newPassword!!42"
|
|
|
|
exception_thrown = False
|
|
try:
|
|
net.change_password(newpassword=password.encode('utf-8'),
|
|
oldpassword="badPassword",
|
|
username=USER_NAME)
|
|
except Exception:
|
|
exception_thrown = True
|
|
self.assertEquals(True, exception_thrown,
|
|
"Expected exception not thrown")
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
self.assertEquals(8,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
# net rap password changes are broken, but they trigger enough of the
|
|
# server side behaviour to exercise the code paths of interest.
|
|
# if we used the real password it would be too long and does not hash
|
|
# correctly, so we just check it triggers the wrong password path.
|
|
def test_rap_change_password(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"SAMR Password Change") and
|
|
(msg["Authentication"]["status"] ==
|
|
"NT_STATUS_WRONG_PASSWORD") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"OemChangePasswordUser2"))
|
|
|
|
username = os.environ["USERNAME"]
|
|
server = os.environ["SERVER"]
|
|
password = os.environ["PASSWORD"]
|
|
server_param = "--server=%s" % server
|
|
creds = "-U%s%%%s" % (username, password)
|
|
call(["bin/net", "rap", server_param,
|
|
"password", USER_NAME, "notMyPassword", "notGoingToBeMyPassword",
|
|
server, creds, "--option=client ipc max protocol=nt1"])
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
self.assertEquals(7,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
def test_ldap_change_password(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] == "NT_STATUS_OK") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"LDAP Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"LDAP Modify"))
|
|
|
|
new_password = samba.generate_random_password(32, 32)
|
|
self.ldb.modify_ldif(
|
|
"dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
|
|
"changetype: modify\n" +
|
|
"delete: userPassword\n" +
|
|
"userPassword: " + USER_PASS + "\n" +
|
|
"add: userPassword\n" +
|
|
"userPassword: " + new_password + "\n")
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
print("Received %d messages" % len(messages))
|
|
self.assertEquals(4,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
#
|
|
# Currently this does not get logged, so we expect to only see the log
|
|
# entries for the underlying ldap bind.
|
|
#
|
|
def test_ldap_change_password_bad_user(self):
|
|
def isLastExpectedMessage(msg):
|
|
return (msg["type"] == "Authorization" and
|
|
msg["Authorization"]["serviceDescription"] == "LDAP" and
|
|
msg["Authorization"]["authType"] == "krb5")
|
|
|
|
new_password = samba.generate_random_password(32, 32)
|
|
try:
|
|
self.ldb.modify_ldif(
|
|
"dn: cn=" + "badUser" + ",cn=users," + self.base_dn + "\n" +
|
|
"changetype: modify\n" +
|
|
"delete: userPassword\n" +
|
|
"userPassword: " + USER_PASS + "\n" +
|
|
"add: userPassword\n" +
|
|
"userPassword: " + new_password + "\n")
|
|
self.fail()
|
|
except LdbError as e:
|
|
(num, msg) = e.args
|
|
pass
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
print("Received %d messages" % len(messages))
|
|
self.assertEquals(3,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|
|
|
|
def test_ldap_change_password_bad_original_password(self):
|
|
def isLastExpectedMessage(msg):
|
|
return ((msg["type"] == "Authentication") and
|
|
(msg["Authentication"]["status"] ==
|
|
"NT_STATUS_WRONG_PASSWORD") and
|
|
(msg["Authentication"]["serviceDescription"] ==
|
|
"LDAP Password Change") and
|
|
(msg["Authentication"]["authDescription"] ==
|
|
"LDAP Modify"))
|
|
|
|
new_password = samba.generate_random_password(32, 32)
|
|
try:
|
|
self.ldb.modify_ldif(
|
|
"dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
|
|
"changetype: modify\n" +
|
|
"delete: userPassword\n" +
|
|
"userPassword: " + "badPassword" + "\n" +
|
|
"add: userPassword\n" +
|
|
"userPassword: " + new_password + "\n")
|
|
self.fail()
|
|
except LdbError as e1:
|
|
(num, msg) = e1.args
|
|
pass
|
|
|
|
messages = self.waitForMessages(isLastExpectedMessage)
|
|
print("Received %d messages" % len(messages))
|
|
self.assertEquals(4,
|
|
len(messages),
|
|
"Did not receive the expected number of messages")
|