whitespace: auth_log_pass_change.py python conventions
[samba.git] / python / samba / tests / auth_log_pass_change.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17
18 """Tests for the Auth and AuthZ logging of password changes.
19 """
20
21 from samba import auth
22 import samba.tests
23 from samba.messaging import Messaging
24 from samba.samdb import SamDB
25 from samba.auth import system_session
26 import json
27 import os
28 import samba.tests.auth_log_base
29 from samba.tests import delete_force
30 from samba.net import Net
31 from samba import ntstatus
32 import samba
33 from subprocess import call
34 from ldb import LdbError
35
36 USER_NAME = "authlogtestuser"
37 USER_PASS = samba.generate_random_password(32,32)
38
39 class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
40
41     def setUp(self):
42         super(AuthLogPassChangeTests, self).setUp()
43
44         self.remoteAddress = os.environ["CLIENT_IP"]
45         self.server_ip = os.environ["SERVER_IP"]
46
47         host = "ldap://%s" % os.environ["SERVER"]
48         self.ldb = SamDB(url=host,
49                          session_info=system_session(),
50                          credentials=self.get_credentials(),
51                          lp=self.get_loadparm())
52
53         print "ldb %s" % type(self.ldb)
54         # Gets back the basedn
55         base_dn = self.ldb.domain_dn()
56         print "base_dn %s" % base_dn
57
58         # Gets back the configuration basedn
59         configuration_dn = self.ldb.get_config_basedn().get_linearized()
60
61         # Get the old "dSHeuristics" if it was set
62         dsheuristics = self.ldb.get_dsheuristics()
63
64         # Set the "dSHeuristics" to activate the correct "userPassword"
65         # behaviour
66         self.ldb.set_dsheuristics("000000001")
67
68         # Reset the "dSHeuristics" as they were before
69         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
70
71         # Get the old "minPwdAge"
72         minPwdAge = self.ldb.get_minPwdAge()
73
74         # Set it temporarily to "0"
75         self.ldb.set_minPwdAge("0")
76         self.base_dn = self.ldb.domain_dn()
77
78         # Reset the "minPwdAge" as it was before
79         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
80
81         # (Re)adds the test user USER_NAME with password USER_PASS
82         delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
83         self.ldb.add({
84              "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
85              "objectclass": "user",
86              "sAMAccountName": USER_NAME,
87              "userPassword": USER_PASS
88         })
89
90         # discard any auth log messages for the password setup
91         self.discardMessages()
92
93     def tearDown(self):
94         super(AuthLogPassChangeTests, self).tearDown()
95
96
97     def test_admin_change_password(self):
98         def isLastExpectedMessage(msg):
99             return (msg["type"] == "Authentication" and
100                     msg["Authentication"]["status"]
101                         == "NT_STATUS_OK" and
102                     msg["Authentication"]["serviceDescription"]
103                         == "SAMR Password Change" and
104                     msg["Authentication"]["authDescription"]
105                         == "samr_ChangePasswordUser3")
106
107         creds = self.insta_creds(template = self.get_credentials())
108
109         lp = self.get_loadparm()
110         net = Net(creds, lp, server=self.server_ip)
111         password = "newPassword!!42"
112
113         net.change_password(newpassword=password.encode('utf-8'),
114                             username=USER_NAME,
115                             oldpassword=USER_PASS)
116
117
118         messages = self.waitForMessages(isLastExpectedMessage)
119         print "Received %d messages" % len(messages)
120         self.assertEquals(8,
121                           len(messages),
122                           "Did not receive the expected number of messages")
123
124     def test_admin_change_password_new_password_fails_restriction(self):
125         def isLastExpectedMessage(msg):
126             return (msg["type"] == "Authentication" and
127                     msg["Authentication"]["status"]
128                         == "NT_STATUS_PASSWORD_RESTRICTION" and
129                     msg["Authentication"]["serviceDescription"]
130                         == "SAMR Password Change" and
131                     msg["Authentication"]["authDescription"]
132                         == "samr_ChangePasswordUser3")
133
134         creds = self.insta_creds(template=self.get_credentials())
135
136         lp = self.get_loadparm()
137         net = Net(creds, lp, server=self.server_ip)
138         password = "newPassword"
139
140         exception_thrown = False
141         try:
142             net.change_password(newpassword=password.encode('utf-8'),
143                                 oldpassword=USER_PASS,
144                                 username=USER_NAME)
145         except Exception, msg:
146             exception_thrown = True
147         self.assertEquals(True, exception_thrown,
148                           "Expected exception not thrown")
149
150         messages = self.waitForMessages(isLastExpectedMessage)
151         self.assertEquals(8,
152                           len(messages),
153                           "Did not receive the expected number of messages")
154
155     def test_admin_change_password_unknown_user(self):
156         def isLastExpectedMessage(msg):
157             return (msg["type"] == "Authentication" and
158                     msg["Authentication"]["status"]
159                         == "NT_STATUS_NO_SUCH_USER" and
160                     msg["Authentication"]["serviceDescription"]
161                         == "SAMR Password Change" and
162                     msg["Authentication"]["authDescription"]
163                         == "samr_ChangePasswordUser3")
164
165         creds = self.insta_creds(template=self.get_credentials())
166
167         lp = self.get_loadparm()
168         net = Net(creds, lp, server=self.server_ip)
169         password = "newPassword!!42"
170
171         exception_thrown = False
172         try:
173             net.change_password(newpassword=password.encode('utf-8'),
174                                 oldpassword=USER_PASS,
175                                 username="badUser")
176         except Exception, msg:
177             exception_thrown = True
178         self.assertEquals(True, exception_thrown,
179                           "Expected exception not thrown")
180
181         messages = self.waitForMessages(isLastExpectedMessage)
182         self.assertEquals(8,
183                           len(messages),
184                           "Did not receive the expected number of messages")
185
186     def test_admin_change_password_bad_original_password(self):
187         def isLastExpectedMessage(msg):
188             return (msg["type"] == "Authentication" and
189                     msg["Authentication"]["status"]
190                         == "NT_STATUS_WRONG_PASSWORD" and
191                     msg["Authentication"]["serviceDescription"]
192                         == "SAMR Password Change" and
193                     msg["Authentication"]["authDescription"]
194                         == "samr_ChangePasswordUser3")
195
196         creds = self.insta_creds(template=self.get_credentials())
197
198         lp = self.get_loadparm()
199         net = Net(creds, lp, server=self.server_ip)
200         password = "newPassword!!42"
201
202         exception_thrown = False
203         try:
204             net.change_password(newpassword=password.encode('utf-8'),
205                                 oldpassword="badPassword",
206                                 username=USER_NAME)
207         except Exception, msg:
208             exception_thrown = True
209         self.assertEquals(True, exception_thrown,
210                           "Expected exception not thrown")
211
212         messages = self.waitForMessages(isLastExpectedMessage)
213         self.assertEquals(8,
214                           len(messages),
215                           "Did not receive the expected number of messages")
216
217     # net rap password changes are broken, but they trigger enough of the
218     # server side behaviour to exercise the code paths of interest.
219     # if we used the real password it would be too long and does not hash
220     # correctly, so we just check it triggers the wrong password path.
221     def test_rap_change_password(self):
222         def isLastExpectedMessage(msg):
223             return (msg["type"] == "Authentication" and
224                     msg["Authentication"]["serviceDescription"]
225                         == "SAMR Password Change" and
226                     msg["Authentication"]["status"]
227                         == "NT_STATUS_WRONG_PASSWORD" and
228                     msg["Authentication"]["authDescription"]
229                         == "OemChangePasswordUser2")
230
231         username = os.environ["USERNAME"]
232         server = os.environ["SERVER"]
233         password = os.environ["PASSWORD"]
234         server_param = "--server=%s" % server
235         creds = "-U%s%%%s" % (username,password)
236         call(["bin/net", "rap", server_param,
237               "password", USER_NAME, "notMyPassword", "notGoingToBeMyPassword",
238               server, creds, "--option=client ipc max protocol=nt1"])
239
240         messages = self.waitForMessages(isLastExpectedMessage)
241         self.assertEquals(7,
242                           len(messages),
243                           "Did not receive the expected number of messages")
244
245     def test_ldap_change_password(self):
246         def isLastExpectedMessage(msg):
247             return (msg["type"] == "Authentication" and
248                     msg["Authentication"]["status"]
249                         == "NT_STATUS_OK" and
250                     msg["Authentication"]["serviceDescription"]
251                         == "LDAP Password Change" and
252                     msg["Authentication"]["authDescription"]
253                         == "LDAP Modify")
254
255         new_password = samba.generate_random_password(32,32)
256         self.ldb.modify_ldif(
257             "dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
258             "changetype: modify\n" +
259             "delete: userPassword\n" +
260             "userPassword: " + USER_PASS + "\n" +
261             "add: userPassword\n" +
262             "userPassword: " + new_password + "\n"
263         )
264
265         messages = self.waitForMessages(isLastExpectedMessage)
266         print "Received %d messages" % len(messages)
267         self.assertEquals(4,
268                           len(messages),
269                           "Did not receive the expected number of messages")
270
271     #
272     # Currently this does not get logged, so we expect to only see the log
273     # entries for the underlying ldap bind.
274     #
275     def test_ldap_change_password_bad_user(self):
276         def isLastExpectedMessage(msg):
277             return (msg["type"] == "Authorization" and
278                     msg["Authorization"]["serviceDescription"]
279                         == "LDAP" and
280                     msg["Authorization"]["authType"] == "krb5")
281
282         new_password = samba.generate_random_password(32,32)
283         try:
284             self.ldb.modify_ldif(
285                 "dn: cn=" + "badUser" + ",cn=users," + self.base_dn + "\n" +
286                 "changetype: modify\n" +
287                 "delete: userPassword\n" +
288                 "userPassword: " + USER_PASS + "\n" +
289                 "add: userPassword\n" +
290                 "userPassword: " + new_password + "\n"
291             )
292             self.fail()
293         except LdbError, (num, msg):
294             pass
295
296         messages = self.waitForMessages(isLastExpectedMessage)
297         print "Received %d messages" % len(messages)
298         self.assertEquals(3,
299                           len(messages),
300                           "Did not receive the expected number of messages")
301
302     def test_ldap_change_password_bad_original_password(self):
303         def isLastExpectedMessage(msg):
304             return (msg["type"] == "Authentication" and
305                     msg["Authentication"]["status"]
306                         == "NT_STATUS_WRONG_PASSWORD" and
307                     msg["Authentication"]["serviceDescription"]
308                         == "LDAP Password Change" and
309                     msg["Authentication"]["authDescription"]
310                         == "LDAP Modify")
311
312         new_password = samba.generate_random_password(32,32)
313         try:
314             self.ldb.modify_ldif(
315                 "dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
316                 "changetype: modify\n" +
317                 "delete: userPassword\n" +
318                 "userPassword: " + "badPassword" + "\n" +
319                 "add: userPassword\n" +
320                 "userPassword: " + new_password + "\n"
321             )
322             self.fail()
323         except LdbError, (num, msg):
324             pass
325
326         messages = self.waitForMessages(isLastExpectedMessage)
327         print "Received %d messages" % len(messages)
328         self.assertEquals(4,
329                           len(messages),
330                           "Did not receive the expected number of messages")