complete.patch
tests/test_ldap.py | ||
---|---|---|
49 | 49 |
pytestmark = pytest.mark.skipif(not has_slapd(), reason='slapd is not installed') |
50 | 50 | |
51 | 51 |
USERNAME = 'etienne.michu' |
52 |
UID = 'etienne.michu'
|
|
52 |
UID = USERNAME
|
|
53 | 53 |
CN = 'Étienne Michu' |
54 | 54 |
DN = 'cn=%s,o=ôrga' % escape_dn_chars(CN) |
55 |
PASS = 'passé'
|
|
56 |
UPASS = 'passé'
|
|
55 |
PASS = 'Passé1234'
|
|
56 |
UPASS = 'Passé1234'
|
|
57 | 57 |
EMAIL = 'etienne.michu@example.net' |
58 | 58 |
CARLICENSE = '123445ABC' |
59 | 59 |
UUID = '8ff2f34a-4a36-103c-8d0a-e3a0333484d3' |
... | ... | |
822 | 822 |
} |
823 | 823 |
] |
824 | 824 |
response = client.post( |
825 |
'/login/', {'login-password-submit': '1', 'username': 'etienne.michu', 'password': PASS}, follow=True
|
|
825 |
'/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True
|
|
826 | 826 |
) |
827 | 827 |
assert Group.objects.count() == 0 |
828 | 828 |
assert response.context['user'].username == '%s@ldap' % USERNAME |
... | ... | |
1418 | 1418 |
'use_tls': False, |
1419 | 1419 |
} |
1420 | 1420 |
] |
1421 |
user = authenticate(username='etienne.michu', password='passé')
|
|
1421 |
user = authenticate(username=USERNAME, password=PASS)
|
|
1422 | 1422 |
assert user |
1423 |
assert user.check_password('passé')
|
|
1423 |
assert user.check_password(PASS)
|
|
1424 | 1424 |
user.set_password('àbon') |
1425 | 1425 |
assert user.check_password('àbon') |
1426 |
user2 = authenticate(username='etienne.michu', password='àbon')
|
|
1426 |
user2 = authenticate(username=USERNAME, password='àbon')
|
|
1427 | 1427 |
assert user.pk == user2.pk |
1428 | 1428 | |
1429 | 1429 |
with mock.patch( |
1430 | 1430 |
'authentic2.backends.ldap_backend.LDAPBackend.modify_password', side_effect=ldap.UNWILLING_TO_PERFORM |
1431 | 1431 |
): |
1432 | 1432 |
with pytest.raises(PasswordChangeError): |
1433 |
user.set_password('passé')
|
|
1433 |
user.set_password(PASS)
|
|
1434 | 1434 |
assert 'set_password failed (UNWILLING_TO_PERFORM)' in caplog.text |
1435 | 1435 | |
1436 | 1436 | |
... | ... | |
1956 | 1956 |
assert caplog.records[1].message == 'Binding to server %s (anonymously)' % slapd.ldap_url |
1957 | 1957 |
assert caplog.records[2].message == ( |
1958 | 1958 |
( |
1959 |
"Created user etienne.michu@ldap (uuid %s) from dn=cn=Étienne Michu,o=ôrga, uid=['etienne.michu'], "
|
|
1959 |
"Created user etienne.michu@ldap (uuid %s) from dn=cn=Étienne Michu,o=ôrga, uid=['%s'], "
|
|
1960 | 1960 |
"sn=['Michu'], givenname=['Étienne'], l=['Paris'], mail=['etienne.michu@example.net'], entryuuid=['%s']" |
1961 | 1961 |
) |
1962 |
% (User.objects.first().uuid, entryuuid) |
|
1962 |
% (User.objects.first().uuid, USERNAME, entryuuid)
|
|
1963 | 1963 |
) |
1964 | 1964 |
assert caplog.records[-1].message == 'Search for (|(mail=*)(uid=*)) returned 6 users.' |
1965 | 1965 | |
... | ... | |
1984 | 1984 |
User.objects.update(first_name='John') |
1985 | 1985 |
management.call_command('sync-ldap-users', verbosity=3) |
1986 | 1986 |
assert caplog.records[2].message == ( |
1987 |
"Updated user etienne.michu@ldap (uuid %s) from dn=cn=Étienne Michu,o=ôrga, uid=['etienne.michu'], "
|
|
1987 |
"Updated user etienne.michu@ldap (uuid %s) from dn=cn=Étienne Michu,o=ôrga, uid=['%s'], "
|
|
1988 | 1988 |
"sn=['Michu'], givenname=['Étienne'], l=['Paris'], mail=['etienne.michu@example.net'], entryuuid=['%s']" |
1989 |
) % (User.objects.first().uuid, entryuuid) |
|
1989 |
) % (User.objects.first().uuid, USERNAME, entryuuid)
|
|
1990 | 1990 | |
1991 | 1991 | |
1992 | 1992 |
def test_get_users_select_realm(slapd, settings, db, caplog): |
... | ... | |
2045 | 2045 |
'givenname': ['Étienne'], |
2046 | 2046 |
'mail': ['etienne.michu@example.net'], |
2047 | 2047 |
'sn': ['Michu'], |
2048 |
'uid': ['etienne.michu'],
|
|
2048 |
'uid': [USERNAME],
|
|
2049 | 2049 |
'carlicense': ['123445ABC'], |
2050 | 2050 |
'entryuuid': None, |
2051 | 2051 |
} |
... | ... | |
2056 | 2056 |
'givenname': ['\xc9tienne'], |
2057 | 2057 |
'mail': ['etienne.michu@example.net'], |
2058 | 2058 |
'sn': ['Michu'], |
2059 |
'uid': ['etienne.michu'],
|
|
2059 |
'uid': [USERNAME],
|
|
2060 | 2060 |
'carlicense': ['123445ABC'], |
2061 | 2061 |
'entryuuid': None, |
2062 | 2062 |
} |
... | ... | |
2073 | 2073 |
'givenname': ['\xc9tienne'], |
2074 | 2074 |
'mail': ['etienne.michu@example.net'], |
2075 | 2075 |
'sn': ['Micho'], |
2076 |
'uid': ['etienne.michu'],
|
|
2076 |
'uid': [USERNAME],
|
|
2077 | 2077 |
'carlicense': ['123445ABC'], |
2078 | 2078 |
'entryuuid': None, |
2079 | 2079 |
} |
... | ... | |
2106 | 2106 |
} |
2107 | 2107 |
] |
2108 | 2108 |
response = client.post( |
2109 |
'/login/', {'login-password-submit': '1', 'username': 'etienne.michu', 'password': PASS}, follow=True
|
|
2109 |
'/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True
|
|
2110 | 2110 |
) |
2111 | 2111 |
user = response.context['user'] |
2112 | 2112 |
fetched_attrs = user.get_attributes(object(), {}) |
... | ... | |
2277 | 2277 |
{ |
2278 | 2278 |
'url': [slapd.ldap_url], |
2279 | 2279 |
'binddn': force_str('cn=%s,o=ôrga' % escape_dn_chars('Étienne Michu')), |
2280 |
'bindpw': 'passé',
|
|
2280 |
'bindpw': PASS,
|
|
2281 | 2281 |
'basedn': 'o=ôrga', |
2282 | 2282 |
'use_tls': False, |
2283 | 2283 |
} |
... | ... | |
2293 | 2293 |
assert 'Base ldapsearch command' in ldap_config_text |
2294 | 2294 |
assert 'ldapsearch -v -H ldapi://' in ldap_config_text |
2295 | 2295 |
assert '-D "cn=Étienne Michu,o=ôrga"' in ldap_config_text |
2296 |
assert '-w "passé"' in ldap_config_text
|
|
2296 |
assert f'-w "{PASS}"' in ldap_config_text
|
|
2297 | 2297 |
assert '-b "o=ôrga"' in ldap_config_text |
2298 | 2298 |
assert '"(|(mail=*)(uid=*))"' in ldap_config_text |
2299 | 2299 | |
2300 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
210 | 210 |
def password_policy_control_messages(ctrl, attributes): |
211 | 211 |
messages = [] |
212 | 212 | |
213 |
if ctrl.error: |
|
213 |
if ctrl.error is not None:
|
|
214 | 214 |
error = ppolicy.PasswordPolicyError.namedValues[ctrl.error] |
215 | 215 |
error2message = { |
216 | 216 |
'passwordExpired': _('The password expired after {pwdmaxage}').format(**attributes), |
217 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
1778 | 1778 |
modlist = [(ldap.MOD_REPLACE, key, [value])] |
1779 | 1779 |
else: |
1780 | 1780 |
key = 'userPassword' |
1781 |
modlist = [(ldap.MOD_REPLACE, key, [new_password])] |
|
1781 |
modlist = [(ldap.MOD_REPLACE, key, [new_password.encode('utf8')])]
|
|
1782 | 1782 |
conn.modify_s(dn, modlist) |
1783 | 1783 |
log.debug('modified password for dn %r', dn) |
1784 | 1784 | |
1785 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
585 | 585 |
return blocks |
586 | 586 | |
587 | 587 |
@classmethod |
588 |
def process_controls(cls, request, block, conn, authz_id, ctrls): |
|
588 |
def process_bind_controls(cls, request, block, conn, authz_id, ctrls):
|
|
589 | 589 |
attributes = cls.get_ppolicy_attributes(block, conn, authz_id) |
590 | 590 |
for c in ctrls: |
591 | 591 |
if c.controlType == ppolicy.PasswordPolicyControl.controlType: |
... | ... | |
723 | 723 |
else: |
724 | 724 |
serverctrls = [] |
725 | 725 |
results = conn.simple_bind_s(authz_id, password, serverctrls=serverctrls) |
726 |
self.process_controls(request, block, conn, authz_id, results[3]) |
|
726 |
self.process_bind_controls(request, block, conn, authz_id, results[3])
|
|
727 | 727 |
user_login_success(authz_id) |
728 | 728 |
if not block['connect_with_user_credentials']: |
729 | 729 |
try: |
... | ... | |
734 | 734 |
break |
735 | 735 |
except ldap.INVALID_CREDENTIALS as e: |
736 | 736 |
if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]: |
737 |
self.process_controls( |
|
737 |
self.process_bind_controls(
|
|
738 | 738 |
request, block, conn, authz_id, DecodeControlTuples(e.args[0]['ctrls']) |
739 | 739 |
) |
740 | 740 |
success, error = self.bind(block, conn) |
tests/test_ldap.py | ||
---|---|---|
2257 | 2257 |
'/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True |
2258 | 2258 |
) |
2259 | 2259 | |
2260 |
def patched_process_controls(cls, request, block, conn, authz_id, ctrls): |
|
2260 |
def patched_process_bind_controls(cls, request, block, conn, authz_id, ctrls):
|
|
2261 | 2261 |
raise exception[0]('oops') |
2262 | 2262 | |
2263 | 2263 |
monkeypatch.setattr( |
2264 | 2264 |
ldap_backend.LDAPBackend, |
2265 |
'process_controls', |
|
2266 |
patched_process_controls, |
|
2265 |
'process_bind_controls',
|
|
2266 |
patched_process_bind_controls,
|
|
2267 | 2267 |
) |
2268 | 2268 |
client.post( |
2269 | 2269 |
'/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True |
2270 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
598 | 598 |
message = str(vars(c)) |
599 | 599 |
log.info('ldap: bind error with authz_id "%s" -> "%s"', authz_id, message) |
600 | 600 | |
601 |
@classmethod |
|
602 |
def process_modify_password_controls(cls, block, conn, authz_id, ctrls): |
|
603 |
attributes = cls.get_ppolicy_attributes(block, conn, authz_id) |
|
604 |
errors = [] |
|
605 |
for c in ctrls: |
|
606 |
if c.controlType == ppolicy.PasswordPolicyControl.controlType: |
|
607 |
message = ' '.join(password_policy_control_messages(c, attributes)) |
|
608 |
else: |
|
609 |
message = str(vars(c)) |
|
610 |
log.info('ldap: fail to modify password of "%s" -> "%s"', authz_id, message) |
|
611 |
errors.append(message) |
|
612 | ||
613 |
if errors: |
|
614 |
raise PasswordChangeError(' '.join(errors)) |
|
615 | ||
601 | 616 |
@classmethod |
602 | 617 |
def check_group_to_role_mappings(cls, block): |
603 | 618 |
group_to_role_mapping = block.get('group_to_role_mapping') |
... | ... | |
1762 | 1777 |
@classmethod |
1763 | 1778 |
def modify_password(cls, conn, block, dn, old_password, new_password): |
1764 | 1779 |
'''Change user password with adaptation for Active Directory''' |
1765 |
if old_password is not None and (block['use_password_modify'] and not block['active_directory']): |
|
1766 |
conn.passwd_s(dn, old_password, new_password) |
|
1767 |
else: |
|
1768 |
modlist = [] |
|
1769 |
if block['active_directory']: |
|
1770 |
key = 'unicodePwd' |
|
1771 |
value = cls.ad_encoding(new_password) |
|
1772 |
if old_password: |
|
1773 |
modlist = [ |
|
1774 |
(ldap.MOD_DELETE, key, [cls.ad_encoding(old_password)]), |
|
1775 |
(ldap.MOD_ADD, key, [value]), |
|
1776 |
] |
|
1777 |
else: |
|
1778 |
modlist = [(ldap.MOD_REPLACE, key, [value])] |
|
1780 |
serverctrls = [] |
|
1781 |
if block.get('use_controls'): |
|
1782 |
serverctrls = [ppolicy.PasswordPolicyControl()] |
|
1783 | ||
1784 |
try: |
|
1785 |
if old_password is not None and (block['use_password_modify'] and not block['active_directory']): |
|
1786 |
results = conn.passwd_s(dn, old_password, new_password, serverctrls=serverctrls) |
|
1779 | 1787 |
else: |
1780 |
key = 'userPassword' |
|
1781 |
modlist = [(ldap.MOD_REPLACE, key, [new_password.encode('utf8')])] |
|
1782 |
conn.modify_s(dn, modlist) |
|
1788 |
modlist = [] |
|
1789 |
if block['active_directory']: |
|
1790 |
attr = 'unicodePwd' |
|
1791 |
value = cls.ad_encoding(new_password) |
|
1792 |
if old_password: |
|
1793 |
modlist = [ |
|
1794 |
(ldap.MOD_DELETE, attr, [cls.ad_encoding(old_password)]), |
|
1795 |
(ldap.MOD_ADD, attr, [value]), |
|
1796 |
] |
|
1797 |
else: |
|
1798 |
modlist = [(ldap.MOD_REPLACE, attr, [value])] |
|
1799 |
else: |
|
1800 |
key = 'userPassword' |
|
1801 |
modlist = [(ldap.MOD_REPLACE, key, [new_password.encode('utf8')])] |
|
1802 |
results = conn.modify_ext_s(dn, modlist, serverctrls=serverctrls) |
|
1803 |
if block.get('use_controls') and len(results) >= 3: |
|
1804 |
cls.process_modify_password_controls(block, conn, dn, results[3]) |
|
1805 |
except ldap.LDAPError as e: |
|
1806 |
if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]: |
|
1807 |
cls.process_modify_password_controls(block, conn, dn, DecodeControlTuples(e.args[0]['ctrls'])) |
|
1808 |
raise |
|
1809 | ||
1783 | 1810 |
log.debug('modified password for dn %r', dn) |
1784 | 1811 | |
1785 | 1812 |
@classmethod |
1786 |
- |
tests/test_ldap.py | ||
---|---|---|
1218 | 1218 |
assert 'LDAP directory refused the password change' in response.text |
1219 | 1219 | |
1220 | 1220 | |
1221 |
def test_user_change_password(slapd, settings, app, db): |
|
1222 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1223 |
{ |
|
1224 |
'url': [slapd.ldap_url], |
|
1225 |
'basedn': 'o=ôrga', |
|
1226 |
'use_tls': False, |
|
1227 |
'user_can_change_password': True, |
|
1228 |
} |
|
1229 |
] |
|
1230 |
assert User.objects.count() == 0 |
|
1231 |
# first login |
|
1232 |
response = app.get('/login/') |
|
1233 |
response.form['username'] = USERNAME |
|
1234 |
response.form['password'] = PASS |
|
1235 |
response = response.form.submit('login-password-submit').follow() |
|
1236 | ||
1237 |
response = app.get('/accounts/password/change/') |
|
1238 |
response.form['old_password'] = PASS |
|
1239 |
response.form['new_password1'] = 'hopAbcde1' |
|
1240 |
response.form['new_password2'] = 'hopAbcde1' |
|
1241 |
response = response.form.submit().follow() |
|
1242 |
assert 'Password changed' in response.text |
|
1243 | ||
1244 | ||
1245 |
def test_login_ppolicy_password_expired(slapd_ppolicy, settings, app, db, caplog): |
|
1246 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1247 |
{ |
|
1248 |
'url': [slapd_ppolicy.ldap_url], |
|
1249 |
'basedn': 'o=ôrga', |
|
1250 |
'use_tls': False, |
|
1251 |
'user_can_change_password': True, |
|
1252 |
'use_controls': True, |
|
1253 |
} |
|
1254 |
] |
|
1255 |
# Add default ppolicy with pwdMaxAge defined |
|
1256 |
pwdMaxAge = 2 |
|
1257 |
slapd_ppolicy.add_ldif( |
|
1258 |
''' |
|
1259 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1260 |
cn: default |
|
1261 |
objectclass: top |
|
1262 |
objectclass: device |
|
1263 |
objectclass: pwdPolicy |
|
1264 |
objectclass: pwdPolicyChecker |
|
1265 |
pwdAttribute: userPassword |
|
1266 |
pwdMaxAge: {pwdMaxAge} |
|
1267 |
'''.format( |
|
1268 |
pwdMaxAge=pwdMaxAge |
|
1269 |
) |
|
1270 |
) |
|
1271 | ||
1272 |
assert User.objects.count() == 0 |
|
1273 |
# first login |
|
1274 |
response = app.get('/login/') |
|
1275 |
response.form['username'] = USERNAME |
|
1276 |
response.form['password'] = PASS |
|
1277 |
response = response.form.submit('login-password-submit').follow() |
|
1278 | ||
1279 |
password = 'hopAbcde1' |
|
1280 |
response = app.get('/accounts/password/change/') |
|
1281 |
response.form['old_password'] = PASS |
|
1282 |
response.form['new_password1'] = password |
|
1283 |
response.form['new_password2'] = password |
|
1284 |
response = response.form.submit().follow() |
|
1285 |
assert 'Password changed' in response.text |
|
1286 | ||
1287 |
response = response.click('Logout') |
|
1288 | ||
1289 |
time.sleep(pwdMaxAge * 2) |
|
1290 | ||
1291 |
response = app.get('/login/') |
|
1292 |
response.form['username'] = USERNAME |
|
1293 |
response.form['password'] = password |
|
1294 |
response = response.form.submit('login-password-submit').maybe_follow() |
|
1295 | ||
1296 |
assert 'The password expired.' in response |
|
1297 | ||
1298 | ||
1299 |
def test_user_change_password_in_history(slapd_ppolicy, settings, app, db): |
|
1300 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1301 |
{ |
|
1302 |
'url': [slapd_ppolicy.ldap_url], |
|
1303 |
'basedn': 'o=ôrga', |
|
1304 |
'use_tls': False, |
|
1305 |
'use_controls': True, |
|
1306 |
'user_can_change_password': True, |
|
1307 |
} |
|
1308 |
] |
|
1309 | ||
1310 |
# Add default ppolicy with pwdInHistory defined |
|
1311 |
slapd_ppolicy.add_ldif( |
|
1312 |
''' |
|
1313 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1314 |
cn: default |
|
1315 |
objectclass: top |
|
1316 |
objectclass: device |
|
1317 |
objectclass: pwdPolicy |
|
1318 |
objectclass: pwdPolicyChecker |
|
1319 |
pwdAttribute: userPassword |
|
1320 |
pwdMinAge: 0 |
|
1321 |
pwdMaxAge: 0 |
|
1322 |
pwdInHistory: 1 |
|
1323 |
''' |
|
1324 |
) |
|
1325 | ||
1326 |
assert User.objects.count() == 0 |
|
1327 |
# first login |
|
1328 |
response = app.get('/login/') |
|
1329 |
response.form['username'] = USERNAME |
|
1330 |
response.form['password'] = PASS |
|
1331 |
response = response.form.submit('login-password-submit').follow() |
|
1332 | ||
1333 |
# change password |
|
1334 |
NEW_PASS = 'hopAbcde1' |
|
1335 |
response = app.get('/accounts/password/change/') |
|
1336 |
response.form['old_password'] = PASS |
|
1337 |
response.form['new_password1'] = NEW_PASS |
|
1338 |
response.form['new_password2'] = NEW_PASS |
|
1339 |
response = response.form.submit().follow() |
|
1340 |
assert 'Password changed' in response.text |
|
1341 | ||
1342 |
# change password again |
|
1343 |
response = app.get('/accounts/password/change/') |
|
1344 |
response.form['old_password'] = NEW_PASS |
|
1345 |
response.form['new_password1'] = PASS |
|
1346 |
response.form['new_password2'] = PASS |
|
1347 |
response = response.form.submit().maybe_follow() |
|
1348 | ||
1349 |
assert 'This password has already been used and can no longer be used.' in response.text |
|
1350 | ||
1351 | ||
1352 |
def test_user_change_password_too_short(slapd_ppolicy, settings, app, db): |
|
1353 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1354 |
{ |
|
1355 |
'url': [slapd_ppolicy.ldap_url], |
|
1356 |
'basedn': 'o=ôrga', |
|
1357 |
'use_tls': False, |
|
1358 |
'use_controls': True, |
|
1359 |
'user_can_change_password': True, |
|
1360 |
'ppolicy_dn': 'cn=default,ou=ppolicies,o=ôrga', |
|
1361 |
} |
|
1362 |
] |
|
1363 | ||
1364 |
# Add default ppolicy with pwdCheckQuality enabled and pwdMinLength defined |
|
1365 |
pwdMinLength = 15 |
|
1366 |
slapd_ppolicy.add_ldif( |
|
1367 |
''' |
|
1368 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1369 |
cn: default |
|
1370 |
objectclass: top |
|
1371 |
objectclass: device |
|
1372 |
objectclass: pwdPolicy |
|
1373 |
objectclass: pwdPolicyChecker |
|
1374 |
pwdAttribute: userPassword |
|
1375 |
pwdCheckQuality: 1 |
|
1376 |
pwdMinLength: {pwdMinLength} |
|
1377 |
'''.format( |
|
1378 |
pwdMinLength=pwdMinLength |
|
1379 |
) |
|
1380 |
) |
|
1381 | ||
1382 |
assert User.objects.count() == 0 |
|
1383 |
# first login |
|
1384 |
response = app.get('/login/') |
|
1385 |
response.form['username'] = USERNAME |
|
1386 |
response.form['password'] = PASS |
|
1387 |
response = response.form.submit('login-password-submit').follow() |
|
1388 | ||
1389 |
# change password |
|
1390 |
NEW_PASS = 'hopAbcde1' |
|
1391 |
response = app.get('/accounts/password/change/') |
|
1392 |
response.form['old_password'] = PASS |
|
1393 |
response.form['new_password1'] = NEW_PASS |
|
1394 |
response.form['new_password2'] = NEW_PASS |
|
1395 |
response = response.form.submit().maybe_follow() |
|
1396 | ||
1397 |
assert f'The password is too short (minimun length: {pwdMinLength})' in response.text |
|
1398 | ||
1399 | ||
1400 |
def test_user_change_password_too_soon(slapd_ppolicy, settings, app, db): |
|
1401 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1402 |
{ |
|
1403 |
'url': [slapd_ppolicy.ldap_url], |
|
1404 |
'basedn': 'o=ôrga', |
|
1405 |
'use_tls': False, |
|
1406 |
'use_controls': True, |
|
1407 |
'user_can_change_password': True, |
|
1408 |
} |
|
1409 |
] |
|
1410 | ||
1411 |
# Add default ppolicy with pwdMinAge defined |
|
1412 |
slapd_ppolicy.add_ldif( |
|
1413 |
''' |
|
1414 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1415 |
cn: default |
|
1416 |
objectclass: top |
|
1417 |
objectclass: device |
|
1418 |
objectclass: pwdPolicy |
|
1419 |
objectclass: pwdPolicyChecker |
|
1420 |
pwdAttribute: userPassword |
|
1421 |
pwdMinAge: 120 |
|
1422 |
''' |
|
1423 |
) |
|
1424 | ||
1425 |
assert User.objects.count() == 0 |
|
1426 |
# first login |
|
1427 |
response = app.get('/login/') |
|
1428 |
response.form['username'] = USERNAME |
|
1429 |
response.form['password'] = PASS |
|
1430 |
response = response.form.submit('login-password-submit').follow() |
|
1431 | ||
1432 |
# change password |
|
1433 |
NEW_PASS = 'hopAbcde1' |
|
1434 |
response = app.get('/accounts/password/change/') |
|
1435 |
response.form['old_password'] = PASS |
|
1436 |
response.form['new_password1'] = NEW_PASS |
|
1437 |
response.form['new_password2'] = NEW_PASS |
|
1438 |
response = response.form.submit().follow() |
|
1439 |
assert 'Password changed' in response.text |
|
1440 | ||
1441 |
# change password again |
|
1442 |
response = app.get('/accounts/password/change/') |
|
1443 |
response.form['old_password'] = NEW_PASS |
|
1444 |
NEW_PASS += '1' |
|
1445 |
response.form['new_password1'] = NEW_PASS |
|
1446 |
response.form['new_password2'] = NEW_PASS |
|
1447 |
response = response.form.submit().maybe_follow() |
|
1448 | ||
1449 |
assert 'It is too soon to change the password.' in response.text |
|
1450 | ||
1451 | ||
1452 |
def test_reset_password_must_supply_old_password(slapd_ppolicy, settings, app, db, caplog): |
|
1453 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1454 |
{ |
|
1455 |
'url': [slapd_ppolicy.ldap_url], |
|
1456 |
'binddn': force_str(slapd_ppolicy.root_bind_dn), |
|
1457 |
'bindpw': force_str(slapd_ppolicy.root_bind_password), |
|
1458 |
'basedn': 'o=ôrga', |
|
1459 |
'use_tls': False, |
|
1460 |
'use_controls': True, |
|
1461 |
'can_reset_password': True, |
|
1462 |
} |
|
1463 |
] |
|
1464 | ||
1465 |
# Add default ppolicy with pwdSafeModify enabled |
|
1466 |
slapd_ppolicy.add_ldif( |
|
1467 |
''' |
|
1468 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1469 |
cn: default |
|
1470 |
objectclass: top |
|
1471 |
objectclass: device |
|
1472 |
objectclass: pwdPolicy |
|
1473 |
objectclass: pwdPolicyChecker |
|
1474 |
pwdAttribute: userPassword |
|
1475 |
pwdSafeModify: TRUE |
|
1476 |
''' |
|
1477 |
) |
|
1478 | ||
1479 |
assert User.objects.count() == 0 |
|
1480 |
# first login |
|
1481 |
response = app.get('/login/') |
|
1482 |
response.form['username'] = USERNAME |
|
1483 |
response.form['password'] = PASS |
|
1484 |
response = response.form.submit('login-password-submit').follow() |
|
1485 |
assert User.objects.count() == 1 |
|
1486 |
assert 'Étienne Michu' in str(response) |
|
1487 |
user = User.objects.get() |
|
1488 |
assert user.email == EMAIL |
|
1489 |
# logout |
|
1490 |
response = response.click('Logout').maybe_follow() |
|
1491 | ||
1492 |
# password reset |
|
1493 |
response = response.click('Reset it!') |
|
1494 |
response.form['email'] = EMAIL |
|
1495 |
assert len(mail.outbox) == 0 |
|
1496 |
response = response.form.submit() |
|
1497 |
assert response['Location'].endswith('/instructions/') |
|
1498 |
assert len(mail.outbox) == 1 |
|
1499 |
url = utils.get_link_from_mail(mail.outbox[0]) |
|
1500 |
relative_url = url.split('testserver')[1] |
|
1501 |
response = app.get(relative_url, status=200) |
|
1502 |
response.form.set('new_password1', '1234==aA') |
|
1503 |
response.form.set('new_password2', '1234==aA') |
|
1504 | ||
1505 |
response = response.form.submit() |
|
1506 |
assert 'The old password must be supplied.' in response |
|
1507 | ||
1508 | ||
1509 |
def test_user_change_password_not_allowed(slapd_ppolicy, settings, app, db): |
|
1510 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1511 |
{ |
|
1512 |
'url': [slapd_ppolicy.ldap_url], |
|
1513 |
'basedn': 'o=ôrga', |
|
1514 |
'use_tls': False, |
|
1515 |
'use_controls': True, |
|
1516 |
'user_can_change_password': True, |
|
1517 |
} |
|
1518 |
] |
|
1519 | ||
1520 |
# Add default ppolicy with pwdAllowUserChange disabled |
|
1521 |
slapd_ppolicy.add_ldif( |
|
1522 |
''' |
|
1523 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1524 |
cn: default |
|
1525 |
objectclass: top |
|
1526 |
objectclass: device |
|
1527 |
objectclass: pwdPolicy |
|
1528 |
objectclass: pwdPolicyChecker |
|
1529 |
pwdAttribute: userPassword |
|
1530 |
pwdAllowUserChange: FALSE |
|
1531 |
''' |
|
1532 |
) |
|
1533 | ||
1534 |
assert User.objects.count() == 0 |
|
1535 |
# first login |
|
1536 |
response = app.get('/login/') |
|
1537 |
response.form['username'] = USERNAME |
|
1538 |
response.form['password'] = PASS |
|
1539 |
response = response.form.submit('login-password-submit').follow() |
|
1540 | ||
1541 |
# change password |
|
1542 |
NEW_PASS = 'hopAbcde1' |
|
1543 |
response = app.get('/accounts/password/change/') |
|
1544 |
response.form['old_password'] = PASS |
|
1545 |
response.form['new_password1'] = NEW_PASS |
|
1546 |
response.form['new_password2'] = NEW_PASS |
|
1547 |
response = response.form.submit().maybe_follow() |
|
1548 | ||
1549 |
assert 'It is not possible to modify the password.' in response.text |
|
1550 | ||
1551 | ||
1221 | 1552 |
def test_tls(db, tls_slapd, settings, client): |
1222 | 1553 |
conn = tls_slapd.get_connection_admin() |
1223 | 1554 |
conn.modify_s( |
... | ... | |
1441 | 1772 |
'basedn': 'o=ôrga', |
1442 | 1773 |
'use_tls': False, |
1443 | 1774 |
'use_controls': True, |
1775 |
'ppolicy_dn': 'cn=default,ou=ppolicies,o=ôrga', |
|
1444 | 1776 |
} |
1445 | 1777 |
] |
1446 | 1778 | |
... | ... | |
1485 | 1817 |
response.form.set('username', USERNAME) |
1486 | 1818 |
response.form.set('password', 'invalid') |
1487 | 1819 |
response = response.form.submit(name='login-password-submit') |
1488 |
assert 'account is locked' in str(response.pyquery('.messages')) |
|
1820 |
assert 'account is locked since ' in str(response.pyquery('.messages')) |
|
1821 |
assert f'after {pwdMaxFailure} failures' in str(response.pyquery('.messages')) |
|
1489 | 1822 | |
1490 | 1823 | |
1491 | 1824 |
def ppolicy_authenticate_exactly_pwdMaxFailure(slapd_ppolicy, caplog): |
... | ... | |
1690 | 2023 |
} |
1691 | 2024 |
] |
1692 | 2025 | |
2026 |
# Add default ppolicy with pwdMaxAge and pwdExpireWarning defined |
|
1693 | 2027 |
pwdMaxAge = 3600 |
1694 | 2028 |
slapd_ppolicy.add_ldif( |
1695 | 2029 |
''' |
... | ... | |
1700 | 2034 |
objectclass: pwdPolicy |
1701 | 2035 |
objectclass: pwdPolicyChecker |
1702 | 2036 |
pwdAttribute: userPassword |
1703 |
pwdMinAge: 0 |
|
1704 | 2037 |
pwdMaxAge: {pwdMaxAge} |
1705 |
pwdInHistory: 1 |
|
1706 |
pwdCheckQuality: 0 |
|
1707 |
pwdMinLength: 0 |
|
1708 | 2038 |
pwdExpireWarning: {pwdMaxAge} |
1709 |
pwdGraceAuthnLimit: 0 |
|
1710 |
pwdLockout: TRUE |
|
1711 |
pwdLockoutDuration: 0 |
|
1712 |
pwdMaxFailure: 0 |
|
1713 |
pwdMaxRecordedFailure: 0 |
|
1714 |
pwdFailureCountInterval: 0 |
|
1715 |
pwdMustChange: FALSE |
|
1716 |
pwdAllowUserChange: TRUE |
|
1717 |
pwdSafeModify: FALSE |
|
1718 | 2039 |
'''.format( |
1719 | 2040 |
pwdMaxAge=pwdMaxAge |
1720 | 2041 |
) |
... | ... | |
1746 | 2067 |
} |
1747 | 2068 |
] |
1748 | 2069 | |
2070 |
# Add default ppolicy with pwdMaxAge and pwdExpireWarning defined |
|
1749 | 2071 |
pwdMaxAge = 3600 |
1750 | 2072 |
slapd_ppolicy.add_ldif( |
1751 | 2073 |
''' |
... | ... | |
1756 | 2078 |
objectclass: pwdPolicy |
1757 | 2079 |
objectclass: pwdPolicyChecker |
1758 | 2080 |
pwdAttribute: userPassword |
1759 |
pwdMinAge: 0 |
|
1760 | 2081 |
pwdMaxAge: {pwdMaxAge} |
1761 |
pwdInHistory: 1 |
|
1762 |
pwdCheckQuality: 0 |
|
1763 |
pwdMinLength: 0 |
|
1764 | 2082 |
pwdExpireWarning: {pwdMaxAge} |
1765 |
pwdGraceAuthnLimit: 0 |
|
1766 |
pwdLockout: TRUE |
|
1767 |
pwdLockoutDuration: 0 |
|
1768 |
pwdMaxFailure: 0 |
|
1769 |
pwdMaxRecordedFailure: 0 |
|
1770 |
pwdFailureCountInterval: 0 |
|
1771 |
pwdMustChange: FALSE |
|
1772 |
pwdAllowUserChange: TRUE |
|
1773 |
pwdSafeModify: FALSE |
|
1774 | 2083 |
'''.format( |
1775 | 2084 |
pwdMaxAge=pwdMaxAge |
1776 | 2085 |
) |
tox.ini | ||
---|---|---|
69 | 69 |
uwsgidecorators |
70 | 70 |
enum34<=1.1.6 |
71 | 71 |
ldaptools>=0.24 |
72 |
python-ldap>=3.3.1 |
|
72 | 73 |
numpy |
73 | 74 |
django-filter |
74 | 75 |
stable-backports: djangorestframework>=3.12,<3.13 |
75 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
592 | 592 |
message = ' '.join(password_policy_control_messages(c, attributes)) |
593 | 593 |
if request is not None: |
594 | 594 |
messages.add_message(request, messages.WARNING, message) |
595 |
if c.graceAuthNsRemaining or c.timeBeforeExpiration: |
|
595 |
if ( |
|
596 |
c.graceAuthNsRemaining |
|
597 |
or c.timeBeforeExpiration |
|
598 |
or ( |
|
599 |
c.error is not None |
|
600 |
and ppolicy.PasswordPolicyError.namedValues[c.error] == 'changeAfterReset' |
|
601 |
) |
|
602 |
): |
|
596 | 603 |
request.needs_password_change = True |
597 | 604 |
else: |
598 | 605 |
message = str(vars(c)) |
src/authentic2/views.py | ||
---|---|---|
797 | 797 |
) |
798 | 798 |
elif username: |
799 | 799 |
request.journal.record('user.login.failure', authenticator=authenticator, username=username) |
800 | ||
801 |
if hasattr(request, 'needs_password_change'): |
|
802 |
del request.needs_password_change |
|
803 |
return utils_misc.redirect( |
|
804 |
request, |
|
805 |
'password_reset', |
|
806 |
resolve=True, |
|
807 |
params={'next': utils_misc.select_next_url(request, '')}, |
|
808 |
) |
|
809 | ||
800 | 810 |
context['form'] = form |
801 | 811 |
return render(request, 'authentic2/login_password_form.html', context) |
802 | 812 |
tests/test_ldap.py | ||
---|---|---|
1506 | 1506 |
assert 'The old password must be supplied.' in response |
1507 | 1507 | |
1508 | 1508 | |
1509 |
def test_login_ppolicy_must_change_password_after_locked(slapd_ppolicy, settings, db, app): |
|
1510 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1511 |
{ |
|
1512 |
'url': [slapd_ppolicy.ldap_url], |
|
1513 |
'basedn': 'o=ôrga', |
|
1514 |
'use_tls': False, |
|
1515 |
'use_controls': True, |
|
1516 |
'can_reset_password': True, |
|
1517 |
'ppolicy_dn': 'cn=default,ou=ppolicies,o=ôrga', |
|
1518 |
} |
|
1519 |
] |
|
1520 | ||
1521 |
# Add default ppolicy with pwdMaxFailure defined and pwdMustChange enabled |
|
1522 |
pwdMaxFailure = 2 |
|
1523 |
slapd_ppolicy.add_ldif( |
|
1524 |
''' |
|
1525 |
dn: cn=default,ou=ppolicies,o=ôrga |
|
1526 |
cn: default |
|
1527 |
objectclass: top |
|
1528 |
objectclass: device |
|
1529 |
objectclass: pwdPolicy |
|
1530 |
objectclass: pwdPolicyChecker |
|
1531 |
pwdAttribute: userPassword |
|
1532 |
pwdLockout: TRUE |
|
1533 |
pwdMaxFailure: {pwdMaxFailure} |
|
1534 |
pwdMustChange: TRUE |
|
1535 |
'''.format( |
|
1536 |
pwdMaxFailure=pwdMaxFailure |
|
1537 |
) |
|
1538 |
) |
|
1539 | ||
1540 |
# Locked account after some login errors |
|
1541 |
for _ in range(pwdMaxFailure): |
|
1542 |
response = app.get('/login/') |
|
1543 |
response.form.set('username', USERNAME) |
|
1544 |
response.form.set('password', 'invalid') |
|
1545 |
response = response.form.submit(name='login-password-submit') |
|
1546 |
assert 'Incorrect Username or password' in str(response.pyquery('.errornotice')) |
|
1547 |
assert 'account is locked' not in str(response.pyquery('.messages')) |
|
1548 |
response = app.get('/login/') |
|
1549 |
response.form.set('username', USERNAME) |
|
1550 |
response.form.set('password', 'invalid') |
|
1551 |
response = response.form.submit(name='login-password-submit') |
|
1552 | ||
1553 |
assert 'account is locked since ' in str(response.pyquery('.messages')) |
|
1554 |
assert f'after {pwdMaxFailure} failures' in str(response.pyquery('.messages')) |
|
1555 | ||
1556 |
# Unlock account and force passwor reset |
|
1557 |
conn = slapd_ppolicy.get_connection_admin() |
|
1558 |
ldif = [ |
|
1559 |
(ldap.MOD_DELETE, 'pwdAccountLockedTime', None), |
|
1560 |
(ldap.MOD_ADD, 'pwdReset', [b'TRUE']), |
|
1561 |
] |
|
1562 |
conn.modify_s(DN, ldif) |
|
1563 | ||
1564 |
# Login with the right password |
|
1565 |
next_url = '/' |
|
1566 |
response = app.get(f'/login/?next={next_url}') |
|
1567 |
response.form.set('username', USERNAME) |
|
1568 |
response.form.set('password', PASS) |
|
1569 |
response = response.form.submit(name='login-password-submit') |
|
1570 | ||
1571 |
assert '/password/reset/' in response['Location'] |
|
1572 |
assert f'next={next_url}' in response['Location'] |
|
1573 |
response = response.follow() |
|
1574 |
assert 'The password was reset and must be changed.' in str(response.pyquery('.messages')) |
|
1575 | ||
1576 | ||
1509 | 1577 |
def test_user_change_password_not_allowed(slapd_ppolicy, settings, app, db): |
1510 | 1578 |
settings.LDAP_AUTH_SETTINGS = [ |
1511 | 1579 |
{ |
1512 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
213 | 213 |
if ctrl.error is not None: |
214 | 214 |
error = ppolicy.PasswordPolicyError.namedValues[ctrl.error] |
215 | 215 |
error2message = { |
216 |
'passwordExpired': _('The password expired after {pwdmaxage}').format(**attributes), |
|
217 |
'accountLocked': _( |
|
218 |
'The account is locked since {pwdaccountlockedtime[0]} after {pwdmaxfailure} failures.' |
|
219 |
).format(**attributes), |
|
216 |
'passwordExpired': _('The password expired.'), |
|
217 |
'accountLocked': _('The account is locked{since} after {failures_count}.').format( |
|
218 |
since=(_(" since %s") % attributes['pwdaccountlockedtime'][0]) |
|
219 |
if attributes['pwdaccountlockedtime'] |
|
220 |
else "", |
|
221 |
failures_count=(_("%s failures") % attributes['pwdmaxfailure'][0]) |
|
222 |
if attributes['pwdmaxfailure'] |
|
223 |
else _("multiple failures"), |
|
224 |
), |
|
220 | 225 |
'changeAfterReset': _('The password was reset and must be changed.'), |
221 | 226 |
'passwordModNotAllowed': _('It is not possible to modify the password.'), |
222 | 227 |
'mustSupplyOldPassword': _('The old password must be supplied.'), |
223 | 228 |
'insufficientPasswordQuality': _('The password does not meet the quality requirements.'), |
224 |
'passwordTooShort': _('The password is too short {pwdminlength}.').format(**attributes), |
|
225 |
'passwordTooYoung': _('It is too soon to change the password {pwdminage}.').format(**attributes), |
|
226 |
'passwordInHistory': _( |
|
227 |
'This password is among the last {pwdhistory} password that were used and cannot be used' |
|
228 |
' again.' |
|
229 |
).format(**attributes), |
|
229 |
'passwordTooShort': _('The password is too short{minlength}.').format( |
|
230 |
minlength=(" (minimun length: %s)" % attributes['pwdminlength'][0]) |
|
231 |
if attributes['pwdminlength'] |
|
232 |
else "" |
|
233 |
), |
|
234 |
'passwordTooYoung': _('It is too soon to change the password.'), |
|
235 |
'passwordInHistory': _('This password has already been used and can no longer be used.'), |
|
230 | 236 |
} |
231 | 237 |
messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error))) |
232 | 238 |
return messages |
233 |
- |
src/authentic2/backends/ldap_backend.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import base64 |
18 | 18 |
import collections |
19 |
import datetime |
|
19 | 20 |
import hashlib |
20 | 21 |
import json |
21 | 22 |
import logging |
... | ... | |
38 | 39 |
from django.core.exceptions import ImproperlyConfigured |
39 | 40 |
from django.db.models import Q |
40 | 41 |
from django.db.transaction import atomic |
42 |
from django.utils.dateformat import format as dateformat |
|
41 | 43 |
from django.utils.encoding import force_bytes, force_str |
42 | 44 |
from django.utils.translation import gettext as _ |
43 | 45 |
from django.utils.translation import ngettext |
... | ... | |
238 | 240 |
return messages |
239 | 241 | |
240 | 242 |
if ctrl.timeBeforeExpiration: |
241 |
expiration_date = time.asctime(time.localtime(time.time() + ctrl.timeBeforeExpiration))
|
|
243 |
expiration_date = datetime.datetime.fromtimestamp(time.time() + ctrl.timeBeforeExpiration)
|
|
242 | 244 |
messages.append( |
243 |
_('The password will expire at {expiration_date}.').format(expiration_date=expiration_date) |
|
245 |
_('The password will expire at {expiration_date}.').format( |
|
246 |
expiration_date=dateformat(expiration_date, 'l j F Y, P') |
|
247 |
) |
|
244 | 248 |
) |
245 | 249 |
if ctrl.graceAuthNsRemaining: |
246 | 250 |
messages.append( |
247 |
- |
src/authentic2/views.py | ||
---|---|---|
1909 | 1909 |
try: |
1910 | 1910 |
response = super().form_valid(form) |
1911 | 1911 |
except utils_misc.PasswordChangeError as e: |
1912 |
messages.error(self.request, e.message)
|
|
1913 |
return utils_misc.redirect(self.request, self.post_change_redirect)
|
|
1912 |
form.add_error('new_password1', e.message)
|
|
1913 |
return self.form_invalid(form)
|
|
1914 | 1914 |
messages.info(self.request, _('Password changed')) |
1915 | 1915 |
self.request.journal.record('user.password.change', session=self.request.session) |
1916 | 1916 |
return response |
tests/test_ldap.py | ||
---|---|---|
1214 | 1214 |
with mock.patch( |
1215 | 1215 |
'authentic2.backends.ldap_backend.LDAPBackend.modify_password', side_effect=ldap.UNWILLING_TO_PERFORM |
1216 | 1216 |
): |
1217 |
response = response.form.submit().follow()
|
|
1217 |
response = response.form.submit() |
|
1218 | 1218 |
assert 'LDAP directory refused the password change' in response.text |
1219 | 1219 | |
1220 | 1220 |
tests/test_views.py | ||
---|---|---|
115 | 115 |
): |
116 | 116 |
resp = resp.form.submit() |
117 | 117 | |
118 |
resp = resp.follow() |
|
119 | 118 |
assert 'Password changed' not in resp |
120 | 119 |
assert 'boum!' in resp |
121 | 120 | |
122 |
- |
src/authentic2/views.py | ||
---|---|---|
1119 | 1119 |
def form_valid(self, form): |
1120 | 1120 |
# Changing password by mail validate the email |
1121 | 1121 |
form.user.set_email_verified(True, source='user') |
1122 |
form.save() |
|
1122 |
try: |
|
1123 |
form.save() |
|
1124 |
except utils_misc.PasswordChangeError as e: |
|
1125 |
form.add_error('new_password1', e.message) |
|
1126 |
return self.form_invalid(form) |
|
1123 | 1127 |
hooks.call_hooks('event', name='password-reset-confirm', user=form.user, token=self.token, form=form) |
1124 | 1128 |
logger.info('password reset for user %s with token %r', self.user, self.token.uuid) |
1125 | 1129 |
self.token.delete() |
tests/test_ldap.py | ||
---|---|---|
1169 | 1169 |
assert 'account is from ldap but it could not be retrieved' in caplog.text |
1170 | 1170 | |
1171 | 1171 | |
1172 |
def test_reset_password_refused_by_ldap_server(slapd, settings, app, db, caplog): |
|
1173 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1174 |
{ |
|
1175 |
'url': [slapd.ldap_url], |
|
1176 |
'binddn': force_str(slapd.root_bind_dn), |
|
1177 |
'bindpw': force_str(slapd.root_bind_password), |
|
1178 |
'basedn': 'o=ôrga', |
|
1179 |
'use_tls': False, |
|
1180 |
'attributes': ['uid', 'carLicense'], |
|
1181 |
'can_reset_password': True, |
|
1182 |
} |
|
1183 |
] |
|
1184 | ||
1185 |
assert User.objects.count() == 0 |
|
1186 |
# first login |
|
1187 |
response = app.get('/login/') |
|
1188 |
response.form['username'] = USERNAME |
|
1189 |
response.form['password'] = PASS |
|
1190 |
response = response.form.submit('login-password-submit').follow() |
|
1191 |
assert User.objects.count() == 1 |
|
1192 |
assert 'Étienne Michu' in str(response) |
|
1193 |
user = User.objects.get() |
|
1194 |
assert user.email == EMAIL |
|
1195 |
# logout |
|
1196 |
response = response.click('Logout').maybe_follow() |
|
1197 | ||
1198 |
# password reset |
|
1199 |
response = response.click('Reset it!') |
|
1200 |
response.form['email'] = EMAIL |
|
1201 |
assert len(mail.outbox) == 0 |
|
1202 |
response = response.form.submit() |
|
1203 |
assert response['Location'].endswith('/instructions/') |
|
1204 |
assert len(mail.outbox) == 1 |
|
1205 |
url = utils.get_link_from_mail(mail.outbox[0]) |
|
1206 |
relative_url = url.split('testserver')[1] |
|
1207 |
response = app.get(relative_url, status=200) |
|
1208 |
response.form.set('new_password1', '1234==aA') |
|
1209 |
response.form.set('new_password2', '1234==aA') |
|
1210 | ||
1211 |
# Make LDAP directory as read-only to trigger an error |
|
1212 |
conn = slapd.get_connection_admin() |
|
1213 |
ldif = [ |
|
1214 |
( |
|
1215 |
ldap.MOD_REPLACE, |
|
1216 |
'olcReadOnly', |
|
1217 |
b'TRUE', |
|
1218 |
) |
|
1219 |
] |
|
1220 |
conn.modify_s('olcDatabase={%s}mdb,cn=config' % (slapd.db_index - 1), ldif) |
|
1221 | ||
1222 |
response = response.form.submit() |
|
1223 |
assert 'LDAP directory refused the password change' in response |
|
1224 | ||
1225 | ||
1172 | 1226 |
def test_user_cannot_change_password(slapd, settings, app, db): |
1173 | 1227 |
settings.LDAP_AUTH_SETTINGS = [ |
1174 | 1228 |
{ |
... | ... | |
1506 | 1560 |
assert 'The old password must be supplied.' in response |
1507 | 1561 | |
1508 | 1562 | |
1563 |
def test_reset_by_email_passwords_not_match(app, simple_user, mailoutbox, settings): |
|
1564 |
url = reverse('password_reset') |
|
1565 |
resp = app.get(url, status=200) |
|
1566 |
resp.form.set('email', simple_user.email) |
|
1567 |
assert len(mailoutbox) == 0 |
|
1568 |
settings.DEFAULT_FROM_EMAIL = 'show only addr <noreply@example.net>' |
|
1569 |
resp = resp.form.submit() |
|
1570 |
utils.assert_event('user.password.reset.request', user=simple_user, email=simple_user.email) |
|
1571 |
assert resp['Location'].endswith('/instructions/') |
|
1572 |
resp = resp.follow() |
|
1573 |
assert len(mailoutbox) == 1 |
|
1574 |
url = utils.get_link_from_mail(mailoutbox[0]) |
|
1575 |
relative_url = url.split('testserver')[1] |
|
1576 |
resp = app.get(relative_url, status=200) |
|
1577 |
resp.form.set('new_password1', '1234==aA') |
|
1578 |
resp.form.set('new_password2', '1234') |
|
1579 |
resp = resp.form.submit() |
|
1580 | ||
1581 |
assert 'Passwords do not match.' in resp |
|
1582 | ||
1583 | ||
1509 | 1584 |
def test_login_ppolicy_must_change_password_after_locked(slapd_ppolicy, settings, db, app): |
1510 | 1585 |
settings.LDAP_AUTH_SETTINGS = [ |
1511 | 1586 |
{ |
1512 |
- |