2017-03-20 23:59:45 +03:00
# 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/>.
#
2018-03-09 16:38:42 +03:00
from __future__ import print_function
2017-03-20 23:59:45 +03:00
""" 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
2017-03-17 05:58:17 +03:00
from ldb import LdbError
2018-05-11 02:03:03 +03:00
from samba . tests . password_test import PasswordCommon
2018-12-13 00:20:28 +03:00
from samba . dcerpc . windows_event_ids import (
EVT_ID_SUCCESSFUL_LOGON ,
EVT_ID_UNSUCCESSFUL_LOGON
)
2017-03-20 23:59:45 +03:00
USER_NAME = " authlogtestuser "
2018-04-30 01:35:25 +03:00
USER_PASS = samba . generate_random_password ( 32 , 32 )
2017-03-20 23:59:45 +03:00
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 ( ) )
2018-03-09 16:38:42 +03:00
print ( " ldb %s " % type ( self . ldb ) )
2017-03-20 23:59:45 +03:00
# Gets back the basedn
base_dn = self . ldb . domain_dn ( )
2018-03-09 16:38:42 +03:00
print ( " base_dn %s " % base_dn )
2017-03-20 23:59:45 +03:00
2018-05-11 02:03:03 +03:00
# permit password changes during this test
PasswordCommon . allow_password_changes ( self , self . ldb )
2017-03-20 23:59:45 +03:00
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 ( {
2018-04-30 01:35:25 +03:00
" dn " : " cn= " + USER_NAME + " ,cn=users, " + self . base_dn ,
" objectclass " : " user " ,
" sAMAccountName " : USER_NAME ,
" userPassword " : USER_PASS
2017-03-24 03:52:58 +03:00
} )
2017-03-20 23:59:45 +03:00
# discard any auth log messages for the password setup
self . discardMessages ( )
def tearDown ( self ) :
super ( AuthLogPassChangeTests , self ) . tearDown ( )
def test_admin_change_password ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] == " NT_STATUS_OK " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" SAMR Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" samr_ChangePasswordUser3 " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_SUCCESSFUL_LOGON ) )
2017-03-20 23:59:45 +03:00
2018-04-30 01:35:25 +03:00
creds = self . insta_creds ( template = self . get_credentials ( ) )
2017-03-20 23:59:45 +03:00
lp = self . get_loadparm ( )
net = Net ( creds , lp , server = self . server_ip )
password = " newPassword!!42 "
2018-11-28 17:06:54 +03:00
net . change_password ( newpassword = password ,
2017-03-24 03:52:58 +03:00
username = USER_NAME ,
oldpassword = USER_PASS )
2017-03-20 23:59:45 +03:00
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2018-03-09 16:38:42 +03:00
print ( " Received %d messages " % len ( messages ) )
2017-03-20 23:59:45 +03:00
self . assertEquals ( 8 ,
len ( messages ) ,
" Did not receive the expected number of messages " )
def test_admin_change_password_new_password_fails_restriction ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] ==
" NT_STATUS_PASSWORD_RESTRICTION " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" SAMR Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" samr_ChangePasswordUser3 " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_UNSUCCESSFUL_LOGON ) )
2017-03-20 23:59:45 +03:00
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 :
2018-11-28 17:06:54 +03:00
net . change_password ( newpassword = password ,
2017-03-24 03:52:58 +03:00
oldpassword = USER_PASS ,
username = USER_NAME )
2018-04-30 01:35:25 +03:00
except Exception :
2017-03-20 23:59:45 +03:00
exception_thrown = True
self . assertEquals ( True , exception_thrown ,
" Expected exception not thrown " )
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2017-03-20 23:59:45 +03:00
self . assertEquals ( 8 ,
len ( messages ) ,
" Did not receive the expected number of messages " )
def test_admin_change_password_unknown_user ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] ==
" NT_STATUS_NO_SUCH_USER " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" SAMR Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" samr_ChangePasswordUser3 " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_UNSUCCESSFUL_LOGON ) )
2017-03-20 23:59:45 +03:00
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 :
2018-11-28 17:06:54 +03:00
net . change_password ( newpassword = password ,
2017-03-24 03:52:58 +03:00
oldpassword = USER_PASS ,
username = " badUser " )
2018-04-30 01:35:25 +03:00
except Exception :
2017-03-20 23:59:45 +03:00
exception_thrown = True
self . assertEquals ( True , exception_thrown ,
" Expected exception not thrown " )
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2017-03-20 23:59:45 +03:00
self . assertEquals ( 8 ,
len ( messages ) ,
" Did not receive the expected number of messages " )
def test_admin_change_password_bad_original_password ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] ==
" NT_STATUS_WRONG_PASSWORD " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" SAMR Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" samr_ChangePasswordUser3 " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_UNSUCCESSFUL_LOGON ) )
2017-03-20 23:59:45 +03:00
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 :
2018-11-28 17:06:54 +03:00
net . change_password ( newpassword = password ,
2017-03-24 03:52:58 +03:00
oldpassword = " badPassword " ,
username = USER_NAME )
2018-04-30 01:35:25 +03:00
except Exception :
2017-03-20 23:59:45 +03:00
exception_thrown = True
self . assertEquals ( True , exception_thrown ,
" Expected exception not thrown " )
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2017-03-20 23:59:45 +03:00
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 ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" SAMR Password Change " ) and
( msg [ " Authentication " ] [ " status " ] ==
" NT_STATUS_WRONG_PASSWORD " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" OemChangePasswordUser2 " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_UNSUCCESSFUL_LOGON ) )
2017-03-20 23:59:45 +03:00
2017-03-24 03:52:58 +03:00
username = os . environ [ " USERNAME " ]
server = os . environ [ " SERVER " ]
password = os . environ [ " PASSWORD " ]
2017-03-20 23:59:45 +03:00
server_param = " --server= %s " % server
2018-04-30 01:35:25 +03:00
creds = " -U %s %% %s " % ( username , password )
2017-03-20 23:59:45 +03:00
call ( [ " bin/net " , " rap " , server_param ,
" password " , USER_NAME , " notMyPassword " , " notGoingToBeMyPassword " ,
server , creds , " --option=client ipc max protocol=nt1 " ] )
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2017-03-20 23:59:45 +03:00
self . assertEquals ( 7 ,
len ( messages ) ,
" Did not receive the expected number of messages " )
2017-03-17 05:58:17 +03:00
def test_ldap_change_password ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] == " NT_STATUS_OK " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" LDAP Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" LDAP Modify " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_SUCCESSFUL_LOGON ) )
2018-04-30 01:35:25 +03:00
new_password = samba . generate_random_password ( 32 , 32 )
2017-03-17 05:58:17 +03:00
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 " +
2018-04-30 01:35:25 +03:00
" userPassword: " + new_password + " \n " )
2017-03-17 05:58:17 +03:00
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2018-03-09 16:38:42 +03:00
print ( " Received %d messages " % len ( messages ) )
2017-03-17 05:58:17 +03:00
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 ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2017-03-17 05:58:17 +03:00
return ( msg [ " type " ] == " Authorization " and
2018-04-30 01:35:25 +03:00
msg [ " Authorization " ] [ " serviceDescription " ] == " LDAP " and
2017-03-17 05:58:17 +03:00
msg [ " Authorization " ] [ " authType " ] == " krb5 " )
2018-04-30 01:35:25 +03:00
new_password = samba . generate_random_password ( 32 , 32 )
2017-03-17 05:58:17 +03:00
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 " +
2018-04-30 01:35:25 +03:00
" userPassword: " + new_password + " \n " )
2017-03-17 05:58:17 +03:00
self . fail ( )
2018-02-23 17:32:17 +03:00
except LdbError as e :
( num , msg ) = e . args
2017-03-17 05:58:17 +03:00
pass
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2018-03-09 16:38:42 +03:00
print ( " Received %d messages " % len ( messages ) )
2017-03-17 05:58:17 +03:00
self . assertEquals ( 3 ,
len ( messages ) ,
" Did not receive the expected number of messages " )
def test_ldap_change_password_bad_original_password ( self ) :
2017-03-24 03:52:58 +03:00
def isLastExpectedMessage ( msg ) :
2018-04-30 01:35:25 +03:00
return ( ( msg [ " type " ] == " Authentication " ) and
( msg [ " Authentication " ] [ " status " ] ==
" NT_STATUS_WRONG_PASSWORD " ) and
( msg [ " Authentication " ] [ " serviceDescription " ] ==
" LDAP Password Change " ) and
( msg [ " Authentication " ] [ " authDescription " ] ==
2018-12-13 00:20:28 +03:00
" LDAP Modify " ) and
( msg [ " Authentication " ] [ " eventId " ] ==
EVT_ID_UNSUCCESSFUL_LOGON ) )
2018-04-30 01:35:25 +03:00
new_password = samba . generate_random_password ( 32 , 32 )
2017-03-17 05:58:17 +03:00
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 " +
2018-04-30 01:35:25 +03:00
" userPassword: " + new_password + " \n " )
2017-03-17 05:58:17 +03:00
self . fail ( )
2018-02-23 17:32:17 +03:00
except LdbError as e1 :
( num , msg ) = e1 . args
2017-03-17 05:58:17 +03:00
pass
2017-03-24 03:52:58 +03:00
messages = self . waitForMessages ( isLastExpectedMessage )
2018-03-09 16:38:42 +03:00
print ( " Received %d messages " % len ( messages ) )
2017-03-17 05:58:17 +03:00
self . assertEquals ( 4 ,
len ( messages ) ,
" Did not receive the expected number of messages " )