fix: Handle integrity violations when confirming email address (#5506)
* fix: Handle integrity violations when confirming email address * test: Add tests of confirm_new_email view
This commit is contained in:
parent
5f1f7aa511
commit
c6fbdef6df
|
@ -24,10 +24,11 @@ from pyquery import PyQuery
|
||||||
from unittest import skipIf
|
from unittest import skipIf
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
import django.core.signing
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
import debug # pyflakes:ignore
|
||||||
|
@ -937,6 +938,72 @@ class IetfAuthTests(TestCase):
|
||||||
self.assertEqual(person.personextresource_set.get(name__slug='github_repo').display_name, 'Some display text')
|
self.assertEqual(person.personextresource_set.get(name__slug='github_repo').display_name, 'Some display text')
|
||||||
self.assertIn(person.personextresource_set.first().name.slug, str(person.personextresource_set.first()))
|
self.assertIn(person.personextresource_set.first().name.slug, str(person.personextresource_set.first()))
|
||||||
|
|
||||||
|
def test_confirm_new_email(self):
|
||||||
|
person = PersonFactory()
|
||||||
|
valid_auth = django.core.signing.dumps(
|
||||||
|
[person.user.username, "new_email@example.com"], salt="add_email"
|
||||||
|
)
|
||||||
|
invalid_auth = django.core.signing.dumps(
|
||||||
|
[person.user.username, "not_this_one@example.com"], salt="pepper"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that we check the salt
|
||||||
|
r = self.client.get(
|
||||||
|
urlreverse("ietf.ietfauth.views.confirm_new_email", kwargs={"auth": invalid_auth})
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
r = self.client.post(
|
||||||
|
urlreverse("ietf.ietfauth.views.confirm_new_email", kwargs={"auth": invalid_auth})
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
# Now check that the valid auth works
|
||||||
|
self.assertFalse(
|
||||||
|
person.email_set.filter(address__icontains="new_email@example.com").exists()
|
||||||
|
)
|
||||||
|
confirm_url = urlreverse(
|
||||||
|
"ietf.ietfauth.views.confirm_new_email", kwargs={"auth": valid_auth}
|
||||||
|
)
|
||||||
|
r = self.client.get(confirm_url)
|
||||||
|
self.assertContains(r, urllib.parse.quote(confirm_url), status_code=200)
|
||||||
|
r = self.client.post(confirm_url, data={"action": "confirm"})
|
||||||
|
self.assertContains(r, "has been updated", status_code=200)
|
||||||
|
self.assertTrue(
|
||||||
|
person.email_set.filter(address__icontains="new_email@example.com").exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Authorizing a second time should be handled gracefully
|
||||||
|
r = self.client.post(confirm_url, data={"action": "confirm"})
|
||||||
|
self.assertContains(r, "already includes", status_code=200)
|
||||||
|
|
||||||
|
# Another person should not be able to add the same address and should be told so,
|
||||||
|
# whether they use the same or different letter case
|
||||||
|
other_person = PersonFactory()
|
||||||
|
other_auth = django.core.signing.dumps(
|
||||||
|
[other_person.user.username, "new_email@example.com"], salt="add_email"
|
||||||
|
)
|
||||||
|
r = self.client.post(
|
||||||
|
urlreverse("ietf.ietfauth.views.confirm_new_email", kwargs={"auth": other_auth}),
|
||||||
|
data={"action": "confirm"},
|
||||||
|
)
|
||||||
|
self.assertContains(r, "in use by another user", status_code=200)
|
||||||
|
|
||||||
|
other_auth = django.core.signing.dumps(
|
||||||
|
[other_person.user.username, "NeW_eMaIl@eXaMpLe.CoM"], salt="add_email"
|
||||||
|
)
|
||||||
|
r = self.client.post(
|
||||||
|
urlreverse("ietf.ietfauth.views.confirm_new_email", kwargs={"auth": other_auth}),
|
||||||
|
data={"action": "confirm"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertContains(r, "in use by another user", status_code=200)
|
||||||
|
self.assertFalse(
|
||||||
|
other_person.email_set.filter(address__icontains="new_email@example.com").exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
person.email_set.filter(address__icontains="new_email@example.com").exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OpenIDConnectTests(TestCase):
|
class OpenIDConnectTests(TestCase):
|
||||||
def request_matcher(self, request):
|
def request_matcher(self, request):
|
||||||
|
|
|
@ -53,6 +53,7 @@ from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.views import LoginView
|
from django.contrib.auth.views import LoginView
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
|
from django.db import IntegrityError
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
|
from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
@ -440,8 +441,19 @@ def confirm_new_email(request, auth):
|
||||||
form = NewEmailForm({ "new_email": email })
|
form = NewEmailForm({ "new_email": email })
|
||||||
can_confirm = form.is_valid() and email
|
can_confirm = form.is_valid() and email
|
||||||
new_email_obj = None
|
new_email_obj = None
|
||||||
|
created = False
|
||||||
if request.method == 'POST' and can_confirm and request.POST.get("action") == "confirm":
|
if request.method == 'POST' and can_confirm and request.POST.get("action") == "confirm":
|
||||||
new_email_obj = Email.objects.create(address=email, person=person, origin=username)
|
try:
|
||||||
|
new_email_obj, created = Email.objects.get_or_create(
|
||||||
|
address=email,
|
||||||
|
person=person,
|
||||||
|
defaults={'origin': username},
|
||||||
|
)
|
||||||
|
except IntegrityError:
|
||||||
|
can_confirm = False
|
||||||
|
form.add_error(
|
||||||
|
None, "Email address is in use by another user. Please contact the secretariat for assistance."
|
||||||
|
)
|
||||||
|
|
||||||
return render(request, 'registration/confirm_new_email.html', {
|
return render(request, 'registration/confirm_new_email.html', {
|
||||||
'username': username,
|
'username': username,
|
||||||
|
@ -449,6 +461,7 @@ def confirm_new_email(request, auth):
|
||||||
'can_confirm': can_confirm,
|
'can_confirm': can_confirm,
|
||||||
'form': form,
|
'form': form,
|
||||||
'new_email_obj': new_email_obj,
|
'new_email_obj': new_email_obj,
|
||||||
|
'already_confirmed': new_email_obj and not created,
|
||||||
})
|
})
|
||||||
|
|
||||||
def password_reset(request):
|
def password_reset(request):
|
||||||
|
|
|
@ -13,9 +13,15 @@
|
||||||
<a class="btn btn-primary my-3"
|
<a class="btn btn-primary my-3"
|
||||||
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||||
{% elif new_email_obj %}
|
{% elif new_email_obj %}
|
||||||
<p class="alert alert-success my-3">
|
{% if already_confirmed %}
|
||||||
Your account {{ username }} has been updated to include the email address {{ email|linkify }}.
|
<p class="alert alert-info my-3">
|
||||||
</p>
|
Your account {{ username }} already includes the email address {{ email|linkify }}.
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="alert alert-success my-3">
|
||||||
|
Your account {{ username }} has been updated to include the email address {{ email|linkify }}.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
<a class="btn btn-primary my-3"
|
<a class="btn btn-primary my-3"
|
||||||
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
Loading…
Reference in a new issue