Projet

Général

Profil

0005-auth_fc-account-verification-view-73148.patch

Paul Marillonnet, 18 janvier 2023 16:37

Télécharger (8,58 ko)

Voir les différences:

Subject: [PATCH 5/5] auth_fc: account verification view (#73148)

 src/authentic2_auth_fc/urls.py  |  6 +++
 src/authentic2_auth_fc/views.py | 35 ++++++++++++++
 tests/auth_fc/test_auth_fc.py   | 84 +++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+)
src/authentic2_auth_fc/urls.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from django.contrib.auth.decorators import login_required
17 18
from django.urls import include, path, re_path
18 19

  
19 20
from . import views
......
21 22
fcpatterns = [
22 23
    path('callback/', views.login_or_link, name='fc-login-or-link'),
23 24
    path('callback_logout/', views.logout, name='fc-logout'),
25
    re_path(
26
        r'^verify/(?P<token>[A-Za-z0-9_ -]+)/$',
27
        login_required(views.FcEmailVerificationView.as_view()),
28
        name='fc-verification',
29
    ),
24 30
]
25 31

  
26 32
urlpatterns = [
src/authentic2_auth_fc/views.py
32 32
from django.template.response import TemplateResponse
33 33
from django.urls import reverse
34 34
from django.utils.http import urlencode
35
from django.utils.timezone import now
35 36
from django.utils.translation import gettext as _
36 37
from django.views.generic import FormView, View
37 38
from requests_oauthlib import OAuth2Session
......
673 674

  
674 675

  
675 676
logout = LogoutReturnView.as_view()
677

  
678

  
679
class FcEmailVerificationView(View):
680
    def get(self, request, *args, **kwargs):
681
        token_value = kwargs.get('token')
682
        try:
683
            token = models.FcEmailVerificationToken.objects.get(value=token_value)
684
        except models.FcEmailVerificationToken.DoesNotExist:
685
            messages.error(
686
                request,
687
                _('Invalid account verification request.'),
688
            )
689
            return utils_misc.redirect(request, 'account_management')
690

  
691
        if token.user != request.user:
692
            messages.error(
693
                request,
694
                _('Invalid account verification request.'),
695
            )
696
            return utils_misc.redirect(request, 'account_management')
697

  
698
        if token.expires < now():
699
            messages.error(
700
                request,
701
                _('Your account verification request has expired.'),
702
            )
703
            return utils_misc.redirect(request, 'account_management')
704

  
705
        token.user.set_email_verified(True, source='fc')
706
        messages.info(
707
            request,
708
            _('Your account is now verified.'),
709
        )
710
        return utils_misc.redirect(request, 'account_management')
tests/auth_fc/test_auth_fc.py
112 112

  
113 113
    assert User.objects.count() == 0
114 114
    assert Event.objects.which_references(service).count() == 0
115
    assert models.FcEmailVerificationToken.objects.count() == 0
115 116
    response = franceconnect.handle_authorization(app, response.location, status=302)
116 117
    assert 'fc-state' not in app.cookies
117 118
    assert User.objects.count() == 1
......
125 126
    # check registration email
126 127
    assert len(mailoutbox) == 1
127 128
    assert mailoutbox[0].subject == 'Account creation using FranceConnect'
129
    assert models.FcEmailVerificationToken.objects.count() == 1
130
    token = models.FcEmailVerificationToken.objects.get()
131
    assert len(str(token.value)) == 36
128 132
    for body in (mailoutbox[0].body, mailoutbox[0].alternatives[0][0]):
129 133
        assert 'Hi Ÿuñe Frédérique,' in body
130 134
        assert 'You have just created an account using FranceConnect.' in body
131 135
        assert 'You can complete your account validation' in body
136
        assert f'https://testserver/fc/verify/{token.value}/' in body
132 137

  
133 138
    assert user.verified_attributes.first_name == 'Ÿuñe'
134 139
    assert user.verified_attributes.last_name == 'Frédérique'
......
140 145
    assert last_name_value.last_verified_on
141 146
    assert first_name_value.verification_sources == ['fc']
142 147
    assert last_name_value.verification_sources == ['fc']
148

  
149
    assert not user.email_verified
150
    resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
151
    assert resp.pyquery('li.info').text() == 'Your account is now verified.'
152
    user.refresh_from_db()
153
    assert user.email_verified
154

  
143 155
    assert path(response.location) == '/idp/'
144 156
    assert hooks.event[1]['kwargs']['name'] == 'login'
145 157
    assert hooks.event[1]['kwargs']['service'] == service
......
170 182
    assert 'Your account link to FranceConnect has been deleted' in response
171 183

  
172 184

  
185
def test_email_verification_anonymous_user(app, simple_user):
186
    token = models.FcEmailVerificationToken.create(user=simple_user)
187
    token.sent = True
188
    token.save()
189

  
190
    resp = app.get(f'https://testserver/fc/verify/{token.value}/')
191
    assert resp.location == f'/login/?next=/fc/verify/{token.value}/'
192
    simple_user.refresh_from_db()
193
    assert not simple_user.email_verified
194

  
195
    resp = resp.follow()
196
    resp.form.set('username', simple_user.username)
197
    resp.form.set('password', simple_user.username)
198
    resp = resp.form.submit(name='login-password-submit')
199
    assert resp.location == f'/fc/verify/{token.value}/'
200
    resp = resp.follow()
201
    assert resp.location == '/accounts/'
202
    resp = resp.follow()
203
    assert resp.pyquery('li.info').text() == 'Your account is now verified.'
204
    simple_user.refresh_from_db()
205
    assert simple_user.email_verified
206

  
207

  
208
def test_email_verification_wrong_link(settings, app, franceconnect, hooks, service):
209
    set_service(app, service)
210
    response = app.get('/login/?next=/idp/')
211
    response = response.click(href='callback')
212
    franceconnect.handle_authorization(app, response.location, status=302)
213
    user = User.objects.get()
214

  
215
    # user is logged yet clicks on a wrong link
216
    resp = app.get('https://testserver/fc/verify/01234567-aaaa-bbbb-cccc-abcdabdcabdc/').follow()
217
    assert resp.pyquery('li.error').text() == 'Invalid account verification request.'
218
    user.refresh_from_db()
219
    assert not user.email_verified
220

  
221

  
222
def test_email_verification_expired(settings, app, franceconnect, hooks, service, freezer):
223
    set_service(app, service)
224
    response = app.get('/login/?next=/idp/')
225
    response = response.click(href='callback')
226
    response = franceconnect.handle_authorization(app, response.location, status=302)
227
    user = User.objects.get()
228
    token = models.FcEmailVerificationToken.objects.get()
229
    assert not user.email_verified
230

  
231
    freezer.move_to(datetime.timedelta(hours=50))  # too late by two hours
232
    resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
233
    assert not resp.pyquery('li.info')
234
    assert resp.pyquery('li.error').text() == 'Your account verification request has expired.'
235
    user.refresh_from_db()
236
    assert not user.email_verified
237

  
238

  
239
def test_email_verification_wrong_user(settings, app, franceconnect, user_ou1, hooks, service, mailoutbox):
240
    set_service(app, service)
241
    response = app.get('/login/?next=/idp/')
242
    response = response.click(href='callback')
243
    response = franceconnect.handle_authorization(app, response.location, status=302)
244
    user = User.objects.get(ou=get_default_ou())
245
    token = models.FcEmailVerificationToken.objects.get()
246
    token.user = user_ou1
247
    token.save()
248

  
249
    assert not user.email_verified
250
    resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
251
    assert not resp.pyquery('li.info')
252
    assert resp.pyquery('li.error').text() == 'Invalid account verification request.'
253
    user.refresh_from_db()
254
    assert not user.email_verified
255

  
256

  
173 257
def test_create_no_unicode_collision(settings, app, franceconnect, hooks, service):
174 258
    settings.A2_EMAIL_IS_UNIQUE = True
175 259
    set_service(app, service)
176
-