auth_fc: account verification view (#73148)
This commit is contained in:
parent
6e9590e4c8
commit
25aec13ee8
|
@ -14,6 +14,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
from . import views
|
||||
|
@ -21,6 +22,11 @@ from . import views
|
|||
fcpatterns = [
|
||||
path('callback/', views.login_or_link, name='fc-login-or-link'),
|
||||
path('callback_logout/', views.logout, name='fc-logout'),
|
||||
re_path(
|
||||
r'^verify/(?P<token>[A-Za-z0-9_ -]+)/$',
|
||||
login_required(views.FcEmailVerificationView.as_view()),
|
||||
name='fc-verification',
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -32,6 +32,7 @@ from django.template.loader import render_to_string
|
|||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import FormView, View
|
||||
from requests_oauthlib import OAuth2Session
|
||||
|
@ -673,3 +674,37 @@ class LogoutReturnView(View):
|
|||
|
||||
|
||||
logout = LogoutReturnView.as_view()
|
||||
|
||||
|
||||
class FcEmailVerificationView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
token_value = kwargs.get('token')
|
||||
try:
|
||||
token = models.FcEmailVerificationToken.objects.get(value=token_value)
|
||||
except models.FcEmailVerificationToken.DoesNotExist:
|
||||
messages.error(
|
||||
request,
|
||||
_('Invalid account verification request.'),
|
||||
)
|
||||
return utils_misc.redirect(request, 'account_management')
|
||||
|
||||
if token.user != request.user:
|
||||
messages.error(
|
||||
request,
|
||||
_('Invalid account verification request.'),
|
||||
)
|
||||
return utils_misc.redirect(request, 'account_management')
|
||||
|
||||
if token.expires < now():
|
||||
messages.error(
|
||||
request,
|
||||
_('Your account verification request has expired.'),
|
||||
)
|
||||
return utils_misc.redirect(request, 'account_management')
|
||||
|
||||
token.user.set_email_verified(True, source='fc')
|
||||
messages.info(
|
||||
request,
|
||||
_('Your account is now verified.'),
|
||||
)
|
||||
return utils_misc.redirect(request, 'account_management')
|
||||
|
|
|
@ -112,6 +112,7 @@ def test_create(settings, app, franceconnect, hooks, service, mailoutbox):
|
|||
|
||||
assert User.objects.count() == 0
|
||||
assert Event.objects.which_references(service).count() == 0
|
||||
assert models.FcEmailVerificationToken.objects.count() == 0
|
||||
response = franceconnect.handle_authorization(app, response.location, status=302)
|
||||
assert 'fc-state' not in app.cookies
|
||||
assert User.objects.count() == 1
|
||||
|
@ -125,10 +126,14 @@ def test_create(settings, app, franceconnect, hooks, service, mailoutbox):
|
|||
# check registration email
|
||||
assert len(mailoutbox) == 1
|
||||
assert mailoutbox[0].subject == 'Account creation using FranceConnect'
|
||||
assert models.FcEmailVerificationToken.objects.count() == 1
|
||||
token = models.FcEmailVerificationToken.objects.get()
|
||||
assert len(str(token.value)) == 36
|
||||
for body in (mailoutbox[0].body, mailoutbox[0].alternatives[0][0]):
|
||||
assert 'Hi Ÿuñe Frédérique,' in body
|
||||
assert 'You have just created an account using FranceConnect.' in body
|
||||
assert 'You can complete your account validation' in body
|
||||
assert f'https://testserver/fc/verify/{token.value}/' in body
|
||||
|
||||
assert user.verified_attributes.first_name == 'Ÿuñe'
|
||||
assert user.verified_attributes.last_name == 'Frédérique'
|
||||
|
@ -140,6 +145,13 @@ def test_create(settings, app, franceconnect, hooks, service, mailoutbox):
|
|||
assert last_name_value.last_verified_on
|
||||
assert first_name_value.verification_sources == ['fc']
|
||||
assert last_name_value.verification_sources == ['fc']
|
||||
|
||||
assert not user.email_verified
|
||||
resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
|
||||
assert resp.pyquery('li.info').text() == 'Your account is now verified.'
|
||||
user.refresh_from_db()
|
||||
assert user.email_verified
|
||||
|
||||
assert path(response.location) == '/idp/'
|
||||
assert hooks.event[1]['kwargs']['name'] == 'login'
|
||||
assert hooks.event[1]['kwargs']['service'] == service
|
||||
|
@ -170,6 +182,78 @@ def test_create(settings, app, franceconnect, hooks, service, mailoutbox):
|
|||
assert 'Your account link to FranceConnect has been deleted' in response
|
||||
|
||||
|
||||
def test_email_verification_anonymous_user(app, simple_user):
|
||||
token = models.FcEmailVerificationToken.create(user=simple_user)
|
||||
token.sent = True
|
||||
token.save()
|
||||
|
||||
resp = app.get(f'https://testserver/fc/verify/{token.value}/')
|
||||
assert resp.location == f'/login/?next=/fc/verify/{token.value}/'
|
||||
simple_user.refresh_from_db()
|
||||
assert not simple_user.email_verified
|
||||
|
||||
resp = resp.follow()
|
||||
resp.form.set('username', simple_user.username)
|
||||
resp.form.set('password', simple_user.username)
|
||||
resp = resp.form.submit(name='login-password-submit')
|
||||
assert resp.location == f'/fc/verify/{token.value}/'
|
||||
resp = resp.follow()
|
||||
assert resp.location == '/accounts/'
|
||||
resp = resp.follow()
|
||||
assert resp.pyquery('li.info').text() == 'Your account is now verified.'
|
||||
simple_user.refresh_from_db()
|
||||
assert simple_user.email_verified
|
||||
|
||||
|
||||
def test_email_verification_wrong_link(settings, app, franceconnect, hooks, service):
|
||||
set_service(app, service)
|
||||
response = app.get('/login/?next=/idp/')
|
||||
response = response.click(href='callback')
|
||||
franceconnect.handle_authorization(app, response.location, status=302)
|
||||
user = User.objects.get()
|
||||
|
||||
# user is logged yet clicks on a wrong link
|
||||
resp = app.get('https://testserver/fc/verify/01234567-aaaa-bbbb-cccc-abcdabdcabdc/').follow()
|
||||
assert resp.pyquery('li.error').text() == 'Invalid account verification request.'
|
||||
user.refresh_from_db()
|
||||
assert not user.email_verified
|
||||
|
||||
|
||||
def test_email_verification_expired(settings, app, franceconnect, hooks, service, freezer):
|
||||
set_service(app, service)
|
||||
response = app.get('/login/?next=/idp/')
|
||||
response = response.click(href='callback')
|
||||
response = franceconnect.handle_authorization(app, response.location, status=302)
|
||||
user = User.objects.get()
|
||||
token = models.FcEmailVerificationToken.objects.get()
|
||||
assert not user.email_verified
|
||||
|
||||
freezer.move_to(datetime.timedelta(hours=50)) # too late by two hours
|
||||
resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
|
||||
assert not resp.pyquery('li.info')
|
||||
assert resp.pyquery('li.error').text() == 'Your account verification request has expired.'
|
||||
user.refresh_from_db()
|
||||
assert not user.email_verified
|
||||
|
||||
|
||||
def test_email_verification_wrong_user(settings, app, franceconnect, user_ou1, hooks, service, mailoutbox):
|
||||
set_service(app, service)
|
||||
response = app.get('/login/?next=/idp/')
|
||||
response = response.click(href='callback')
|
||||
response = franceconnect.handle_authorization(app, response.location, status=302)
|
||||
user = User.objects.get(ou=get_default_ou())
|
||||
token = models.FcEmailVerificationToken.objects.get()
|
||||
token.user = user_ou1
|
||||
token.save()
|
||||
|
||||
assert not user.email_verified
|
||||
resp = app.get(f'https://testserver/fc/verify/{token.value}/').follow()
|
||||
assert not resp.pyquery('li.info')
|
||||
assert resp.pyquery('li.error').text() == 'Invalid account verification request.'
|
||||
user.refresh_from_db()
|
||||
assert not user.email_verified
|
||||
|
||||
|
||||
def test_create_no_unicode_collision(settings, app, franceconnect, hooks, service):
|
||||
settings.A2_EMAIL_IS_UNIQUE = True
|
||||
set_service(app, service)
|
||||
|
|
Loading…
Reference in New Issue