1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
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.
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.
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/>.
18 """Tests for the Auth and AuthZ logging of password changes.
21 from samba import auth
23 from samba.messaging import Messaging
24 from samba.samdb import SamDB
25 from samba.auth import system_session
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
33 from subprocess import call
34 from ldb import LdbError
36 USER_NAME = "authlogtestuser"
37 USER_PASS = samba.generate_random_password(32,32)
39 class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
42 super(AuthLogPassChangeTests, self).setUp()
44 self.remoteAddress = os.environ["CLIENT_IP"]
45 self.server_ip = os.environ["SERVER_IP"]
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())
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
58 # Gets back the configuration basedn
59 configuration_dn = self.ldb.get_config_basedn().get_linearized()
61 # Get the old "dSHeuristics" if it was set
62 dsheuristics = self.ldb.get_dsheuristics()
64 # Set the "dSHeuristics" to activate the correct "userPassword"
66 self.ldb.set_dsheuristics("000000001")
68 # Reset the "dSHeuristics" as they were before
69 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
71 # Get the old "minPwdAge"
72 minPwdAge = self.ldb.get_minPwdAge()
74 # Set it temporarily to "0"
75 self.ldb.set_minPwdAge("0")
76 self.base_dn = self.ldb.domain_dn()
78 # Reset the "minPwdAge" as it was before
79 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
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)
84 "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
85 "objectclass": "user",
86 "sAMAccountName": USER_NAME,
87 "userPassword": USER_PASS
90 # discard any auth log messages for the password setup
91 self.discardMessages()
94 super(AuthLogPassChangeTests, self).tearDown()
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")
107 creds = self.insta_creds(template = self.get_credentials())
109 lp = self.get_loadparm()
110 net = Net(creds, lp, server=self.server_ip)
111 password = "newPassword!!42"
113 net.change_password(newpassword=password.encode('utf-8'),
115 oldpassword=USER_PASS)
118 messages = self.waitForMessages(isLastExpectedMessage)
119 print "Received %d messages" % len(messages)
122 "Did not receive the expected number of messages")
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")
134 creds = self.insta_creds(template=self.get_credentials())
136 lp = self.get_loadparm()
137 net = Net(creds, lp, server=self.server_ip)
138 password = "newPassword"
140 exception_thrown = False
142 net.change_password(newpassword=password.encode('utf-8'),
143 oldpassword=USER_PASS,
145 except Exception, msg:
146 exception_thrown = True
147 self.assertEquals(True, exception_thrown,
148 "Expected exception not thrown")
150 messages = self.waitForMessages(isLastExpectedMessage)
153 "Did not receive the expected number of messages")
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")
165 creds = self.insta_creds(template=self.get_credentials())
167 lp = self.get_loadparm()
168 net = Net(creds, lp, server=self.server_ip)
169 password = "newPassword!!42"
171 exception_thrown = False
173 net.change_password(newpassword=password.encode('utf-8'),
174 oldpassword=USER_PASS,
176 except Exception, msg:
177 exception_thrown = True
178 self.assertEquals(True, exception_thrown,
179 "Expected exception not thrown")
181 messages = self.waitForMessages(isLastExpectedMessage)
184 "Did not receive the expected number of messages")
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")
196 creds = self.insta_creds(template=self.get_credentials())
198 lp = self.get_loadparm()
199 net = Net(creds, lp, server=self.server_ip)
200 password = "newPassword!!42"
202 exception_thrown = False
204 net.change_password(newpassword=password.encode('utf-8'),
205 oldpassword="badPassword",
207 except Exception, msg:
208 exception_thrown = True
209 self.assertEquals(True, exception_thrown,
210 "Expected exception not thrown")
212 messages = self.waitForMessages(isLastExpectedMessage)
215 "Did not receive the expected number of messages")
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")
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"])
240 messages = self.waitForMessages(isLastExpectedMessage)
243 "Did not receive the expected number of messages")
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"]
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"
265 messages = self.waitForMessages(isLastExpectedMessage)
266 print "Received %d messages" % len(messages)
269 "Did not receive the expected number of messages")
272 # Currently this does not get logged, so we expect to only see the log
273 # entries for the underlying ldap bind.
275 def test_ldap_change_password_bad_user(self):
276 def isLastExpectedMessage(msg):
277 return (msg["type"] == "Authorization" and
278 msg["Authorization"]["serviceDescription"]
280 msg["Authorization"]["authType"] == "krb5")
282 new_password = samba.generate_random_password(32,32)
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"
293 except LdbError, (num, msg):
296 messages = self.waitForMessages(isLastExpectedMessage)
297 print "Received %d messages" % len(messages)
300 "Did not receive the expected number of messages")
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"]
312 new_password = samba.generate_random_password(32,32)
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"
323 except LdbError, (num, msg):
326 messages = self.waitForMessages(isLastExpectedMessage)
327 print "Received %d messages" % len(messages)
330 "Did not receive the expected number of messages")