Project

General

Profile

0001-ldap-redirect-password-change-if-it-is-about-to-expi.patch

Loïc Dachary, 18 February 2021 06:22 PM

Download (7.43 KB)

View differences:

Subject: [PATCH] ldap: redirect /password/change if it is about to expire
 (#51268)

Fixes: #51268

License: MIT
 src/authentic2/backends/ldap_backend.py |  4 +-
 src/authentic2/constants.py             |  1 +
 src/authentic2/middleware.py            | 13 ++++-
 src/authentic2/settings.py              |  1 +
 tests/test_ldap.py                      | 73 ++++++++++++++++++++++---
 5 files changed, 82 insertions(+), 10 deletions(-)
src/authentic2/backends/ldap_backend.py
56 56

  
57 57
from authentic2.compat_lasso import lasso
58 58

  
59
from authentic2 import crypto, app_settings
59
from authentic2 import crypto, app_settings, constants
60 60
from authentic2.models import UserExternalId
61 61
from authentic2.middleware import StoreRequestMiddleware
62 62
from authentic2.user_login_failure import user_login_failure, user_login_success
......
585 585
                message = ' '.join(password_policy_control_messages(c))
586 586
                if request is not None:
587 587
                    messages.add_message(request, messages.WARNING, message)
588
                    if c.graceAuthNsRemaining or c.timeBeforeExpiration:
589
                        request.session[constants.PASSWORD_EXPIRATION_SESSION_KEY] = True
588 590
            else:
589 591
                message = str(vars(c))
590 592
            log.info('ldap: bind error with authz_id "%s" -> "%s"', authz_id, message)
src/authentic2/constants.py
19 19
CANCEL_FIELD_NAME = 'cancel'
20 20
AUTHENTICATION_EVENTS_SESSION_KEY = 'authentication-events'
21 21
SWITCH_USER_SESSION_KEY = '_switch_user'
22
PASSWORD_EXPIRATION_SESSION_KEY = '_password_expiration'
22 23
LAST_LOGIN_SESSION_KEY = '_last_login'
23 24
SERVICE_FIELD_NAME = 'service'
24 25
NEXT_URL_SIGNATURE = 'next-signature'
src/authentic2/middleware.py
28 28
from django.utils.six.moves.urllib import parse as urlparse
29 29
from django import http
30 30

  
31
from . import app_settings, utils, plugins
31
from . import app_settings, utils, plugins, constants
32 32
from .utils.service import get_service_from_request, get_service_from_session
33 33

  
34 34

  
......
167 167
        return utils.redirect(request, 'continue', resolve=True, params={'next': url})
168 168

  
169 169

  
170
class ChangePasswordAboutToExpireMiddleware(MiddlewareMixin):
171
    '''If the password is about to expire, redirect the user to the password
172
       modification page.
173
    '''
174
    def process_response(self, request, response):
175
        if request.session.get(constants.PASSWORD_EXPIRATION_SESSION_KEY) is None:
176
            return response
177
        del request.session[constants.PASSWORD_EXPIRATION_SESSION_KEY]
178
        return utils.redirect(request, 'password_change', resolve=True)
179

  
180

  
170 181
class ServiceAccessControlMiddleware(MiddlewareMixin):
171 182
    def process_exception(self, request, exception):
172 183
        if not isinstance(exception, (utils.ServiceAccessDenied,)):
src/authentic2/settings.py
92 92
    'authentic2.middleware.StoreRequestMiddleware',
93 93
    'authentic2.middleware.RequestIdMiddleware',
94 94
    'authentic2.middleware.ServiceAccessControlMiddleware',
95
    'authentic2.middleware.ChangePasswordAboutToExpireMiddleware',
95 96
    'authentic2.middleware.CookieTestMiddleware',
96 97
    'django.middleware.common.CommonMiddleware',
97 98
    'django.middleware.http.ConditionalGetMiddleware',
tests/test_ldap.py
742 742
    assert force_bytes('Étienne Michu') in response.body
743 743

  
744 744

  
745
def test_reset_password_ldap_user(slapd, settings, app, db):
746
    settings.LDAP_AUTH_SETTINGS = [{
747
        'url': [slapd.ldap_url],
748
        'binddn': force_text(slapd.root_bind_dn),
749
        'bindpw': force_text(slapd.root_bind_password),
750
        'basedn': u'o=ôrga',
751
        'use_tls': False,
752
    }]
745
def reset_password_ldap_user(settings, app):
753 746
    assert User.objects.count() == 0
754 747
    # first login
755 748
    response = app.get('/login/')
......
781 774
    response.form['new_password1'] = new_password
782 775
    response.form['new_password2'] = new_password
783 776
    response = response.form.submit(status=302).maybe_follow()
777
    # logout
778
    response = response.click('Logout')
779
    if response.status_code == 200:  # Django 1.7, same_origin is bugged; testserver != localhost:80
780
        response = response.form.submit().maybe_follow()
781
    else:
782
        response = response.maybe_follow()
783
    return new_password
784

  
785

  
786
def test_reset_password_ldap_user(slapd, settings, app, db):
787
    settings.LDAP_AUTH_SETTINGS = [{
788
        'url': [slapd.ldap_url],
789
        'binddn': force_text(slapd.root_bind_dn),
790
        'bindpw': force_text(slapd.root_bind_password),
791
        'basedn': u'o=ôrga',
792
        'use_tls': False,
793
    }]
794
    new_password = reset_password_ldap_user(settings, app)
784 795
    # verify password has changed
785 796
    slapd.get_connection().bind_s(DN, new_password)
786 797
    with pytest.raises(ldap.INVALID_CREDENTIALS):
......
1130 1141
    assert 'password will expire' in caplog.text
1131 1142

  
1132 1143

  
1144
def test_login_ppolicy_pwdExpireWarning(slapd_ppolicy, settings, app, db, caplog):
1145
    settings.LDAP_AUTH_SETTINGS = [{
1146
        'url': [slapd_ppolicy.ldap_url],
1147
        'binddn': force_text(slapd_ppolicy.root_bind_dn),
1148
        'bindpw': force_text(slapd_ppolicy.root_bind_password),
1149
        'basedn': u'o=ôrga',
1150
        'use_tls': False,
1151
    }]
1152

  
1153
    pwdMaxAge = 3600
1154
    slapd_ppolicy.add_ldif('''
1155
dn: cn=default,ou=ppolicies,o=ôrga
1156
cn: default
1157
objectclass: top
1158
objectclass: device
1159
objectclass: pwdPolicy
1160
objectclass: pwdPolicyChecker
1161
pwdAttribute: userPassword
1162
pwdMinAge: 0
1163
pwdMaxAge: {pwdMaxAge}
1164
pwdInHistory: 1
1165
pwdCheckQuality: 0
1166
pwdMinLength: 0
1167
pwdExpireWarning: {pwdMaxAge}
1168
pwdGraceAuthnLimit: 0
1169
pwdLockout: TRUE
1170
pwdLockoutDuration: 0
1171
pwdMaxFailure: 0
1172
pwdMaxRecordedFailure: 0
1173
pwdFailureCountInterval: 0
1174
pwdMustChange: FALSE
1175
pwdAllowUserChange: TRUE
1176
pwdSafeModify: FALSE
1177
'''.format(pwdMaxAge=pwdMaxAge))
1178

  
1179
    password = reset_password_ldap_user(settings, app)
1180

  
1181
    time.sleep(2)
1182

  
1183
    response = app.get('/login/')
1184
    response.form['username'] = USERNAME
1185
    response.form['password'] = password
1186
    response = response.form.submit('login-password-submit')
1187
    assert response['Location'].endswith('/password/change/')
1188

  
1189

  
1133 1190
def test_authenticate_ppolicy_pwdAllowUserChange(slapd_ppolicy, settings, db, caplog):
1134 1191
    settings.LDAP_AUTH_SETTINGS = [{
1135 1192
        'url': [slapd_ppolicy.ldap_url],
1136
-