Projet

Général

Profil

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

Benjamin Dauvergne, 17 février 2022 20:00

Télécharger (6,75 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
189 189
edit_required_profile = login_required(EditRequired.as_view())
190 190

  
191 191

  
192
class EmailChangeView(HomeURLMixin, cbv.TemplateNamesMixin, FormView):
192
class RecentAuthenticationMixin:
193
    last_authentication_max_age = 600  # 10 minutes
194

  
195
    def reauthenticate(self, action, message):
196
        methods = [event['how'] for event in utils_misc.get_authentication_events(self.request)]
197
        return utils_misc.login_require(
198
            self.request,
199
            token={
200
                'action': action,
201
                'message': message,
202
                'methods': methods,
203
            },
204
        )
205

  
206
    def has_recent_authentication(self):
207
        age = time.time() - utils_misc.last_authentication_event(request=self.request)['when']
208
        return age < self.last_authentication_max_age
209

  
210

  
211
class EmailChangeView(HomeURLMixin, RecentAuthenticationMixin, cbv.TemplateNamesMixin, FormView):
193 212
    template_names = ['profiles/email_change.html', 'authentic2/change_email.html']
194 213
    title = _('Email Change')
195 214
    success_url = '..'
196 215

  
216
    def can_validate_with_password(self):
217
        last_event = utils_misc.last_authentication_event(self.request)
218
        return last_event and last_event['how'] == 'password-on-https'
219

  
197 220
    def get_form_class(self):
198
        if self.request.user.has_usable_password():
221
        if self.can_validate_with_password():
199 222
            return profile_forms.EmailChangeForm
200 223
        return profile_forms.EmailChangeFormNoPassword
201 224

  
......
204 227
        kwargs['user'] = self.request.user
205 228
        return kwargs
206 229

  
230
    def has_recent_authentication(self):
231
        age = time.time() - utils_misc.last_authentication_event(request=self.request)['when']
232
        return age < self.last_authentication_max_age
233

  
234
    def dispatch(self, request, *args, **kwargs):
235
        if not self.can_validate_with_password() and not self.has_recent_authentication():
236
            return self.reauthenticate(
237
                action='email-change',
238
                message=_('You must re-authenticate to change your email address.'),
239
            )
240
        return super().dispatch(request, *args, **kwargs)
241

  
207 242
    def post(self, request, *args, **kwargs):
208 243
        if 'cancel' in request.POST:
209 244
            return utils_misc.redirect(request, 'account_management')
......
1336 1371
registration_completion = RegistrationCompletionView.as_view()
1337 1372

  
1338 1373

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

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

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

  
1363 1387
    def post(self, request, *args, **kwargs):
1364 1388
        if 'cancel' in request.POST:
1365 1389
            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, set_service
40
from ..utils import assert_event, get_link_from_mail, login, set_service
41 41

  
42 42
User = get_user_model()
43 43

  
......
609 609
    assert User.objects.get(pk=user.pk).email == 'john.doe@example.com'
610 610
    assert User.objects.get(pk=user.pk).first_name == 'Ÿuñe'
611 611
    assert app.session['_auth_user_id'] == str(user.pk)
612

  
613

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