Projet

Général

Profil

0006-views-support-any-kind-of-authentication-for-email-c.patch

Benjamin Dauvergne, 26 janvier 2022 16:12

Télécharger (6,66 ko)

Voir les différences:

Subject: [PATCH 6/6] views: support any kind of authentication for email
 change (#61125)

 src/authentic2/views.py       | 56 +++++++++++++++++++++++++----------
 tests/auth_fc/test_auth_fc.py | 36 +++++++++++++++++++++-
 2 files changed, 75 insertions(+), 17 deletions(-)
src/authentic2/views.py
183 183
edit_required_profile = login_required(EditRequired.as_view())
184 184

  
185 185

  
186
class EmailChangeView(cbv.TemplateNamesMixin, FormView):
186
class RecentAuthenticationMixin:
187
    last_authentication_max_age = 600  # 10 minutes
188

  
189
    def reauthenticate(self, action, message):
190
        methods = [event['how'] for event in utils_misc.get_authentication_events(self.request)]
191
        return utils_misc.login_require(
192
            self.request,
193
            token={
194
                'action': action,
195
                'message': message,
196
                'methods': methods,
197
            },
198
        )
199

  
200
    def has_recent_authentication(self):
201
        age = time.time() - utils_misc.last_authentication_event(request=self.request)['when']
202
        return age < self.last_authentication_max_age
203

  
204

  
205
class EmailChangeView(RecentAuthenticationMixin, cbv.TemplateNamesMixin, FormView):
187 206
    template_names = ['profiles/email_change.html', 'authentic2/change_email.html']
188 207
    title = _('Email Change')
189 208
    success_url = '..'
190 209

  
210
    def can_validate_with_password(self):
211
        last_event = utils_misc.last_authentication_event(self.request)
212
        return last_event and last_event['how'].startswith('password-')
213

  
191 214
    def get_form_class(self):
192
        if self.request.user.has_usable_password():
215
        if self.can_validate_with_password():
193 216
            return profile_forms.EmailChangeForm
194 217
        return profile_forms.EmailChangeFormNoPassword
195 218

  
......
198 221
        kwargs['user'] = self.request.user
199 222
        return kwargs
200 223

  
224
    def has_recent_authentication(self):
225
        age = time.time() - utils_misc.last_authentication_event(request=self.request)['when']
226
        return age < self.last_authentication_max_age
227

  
228
    def dispatch(self, request, *args, **kwargs):
229
        if not self.can_validate_with_password() and not self.has_recent_authentication():
230
            return self.reauthenticate(
231
                action='email-change',
232
                message=_('You must re-authenticate to change your email address.'),
233
            )
234
        return super().dispatch(request, *args, **kwargs)
235

  
201 236
    def post(self, request, *args, **kwargs):
202 237
        if 'cancel' in request.POST:
203 238
            return utils_misc.redirect(request, 'account_management')
......
1335 1370
registration_completion = RegistrationCompletionView.as_view()
1336 1371

  
1337 1372

  
1338
class AccountDeleteView(TemplateView):
1373
class AccountDeleteView(RecentAuthenticationMixin, TemplateView):
1339 1374
    template_name = 'authentic2/accounts_delete_request.html'
1340 1375
    title = _('Request account deletion')
1341
    last_authentication_max_age = 600  # 10 minutes
1342 1376

  
1343 1377
    def dispatch(self, request, *args, **kwargs):
1344 1378
        if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT:
1345 1379
            return utils_misc.redirect(request, '..')
1346 1380
        if not self.request.user.email_verified and not self.has_recent_authentication():
1347
            methods = [event['how'] for event in utils_misc.get_authentication_events(request)]
1348
            return utils_misc.login_require(
1349
                request,
1350
                token={
1351
                    'action': 'account-delete',
1352
                    'message': _('You must re-authenticate to delete your account.'),
1353
                    'methods': methods,
1354
                },
1381
            return self.reauthenticate(
1382
                action='account-delete', message=_('You must re-authenticate to delete your account.')
1355 1383
            )
1356 1384
        return super().dispatch(request, *args, **kwargs)
1357 1385

  
1358
    def has_recent_authentication(self):
1359
        age = time.time() - utils_misc.last_authentication_event(request=self.request)['when']
1360
        return age < self.last_authentication_max_age
1361

  
1362 1386
    def post(self, request, *args, **kwargs):
1363 1387
        if 'cancel' in request.POST:
1364 1388
            return utils_misc.redirect(request, 'account_management')
tests/auth_fc/test_auth_fc.py
37 37
from authentic2_auth_fc.backends import FcBackend
38 38
from authentic2_auth_fc.utils import requests_retry_session
39 39

  
40
from ..utils import get_link_from_mail, login
40
from ..utils import assert_event, get_link_from_mail, login
41 41

  
42 42
User = get_user_model()
43 43

  
......
598 598
    assert User.objects.get(pk=user.pk).email == 'john.doe@example.com'
599 599
    assert User.objects.get(pk=user.pk).first_name == 'Ÿuñe'
600 600
    assert app.session['_auth_user_id'] == str(user.pk)
601

  
602

  
603
def test_change_email(settings, app, franceconnect, mailoutbox, freezer):
604
    response = app.get('/login/?service=portail&next=/idp/')
605
    response = response.click(href='callback')
606
    response = franceconnect.handle_authorization(app, response.location, status=302)
607
    freezer.move_to(datetime.timedelta(hours=1))
608
    redirect = app.get('/accounts/change-email/')
609
    display_message_redirect = redirect.follow()
610
    display_message_page = display_message_redirect.follow()
611
    assert 'You must re-authenticate' in display_message_page
612
    callback_url = display_message_page.pyquery('#a2-continue')[0].attrib['href']
613
    change_email_page = franceconnect.handle_authorization(app, callback_url, status=302).follow()
614
    user = User.objects.get()
615
    assert user.email == 'john.doe@example.com'
616
    change_email_page.form.set('email', 'jane.doe@example.com')
617
    redirect = change_email_page.form.submit()
618
    assert_event(
619
        'user.email.change.request',
620
        user=user,
621
        session=app.session,
622
        old_email='john.doe@example.com',
623
        email='jane.doe@example.com',
624
    )
625
    link = get_link_from_mail(mailoutbox[-1])
626
    app.get(link)
627
    assert_event(
628
        'user.email.change',
629
        user=user,
630
        session=app.session,
631
        old_email='john.doe@example.com',
632
        email='jane.doe@example.com',
633
    )
634
    assert User.objects.get().email == 'jane.doe@example.com'
601
-