This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
from django.http import HttpResponseRedirect
from django.urls import reverse
from allauth.account.adapter import DefaultAccountAdapter
from allauth.core.exceptions import ImmediateHttpResponse
class PreLoginRedirectAccountAdapter(DefaultAccountAdapter):
def pre_login(self, *args, **kwargs):
raise ImmediateHttpResponse(HttpResponseRedirect("/foo"))
def test_adapter_pre_login(settings, user, user_password, client):
settings.ACCOUNT_ADAPTER = (
"allauth.account.tests.test_adapter.PreLoginRedirectAccountAdapter"
)
resp = client.post(
reverse("account_login"),
{"login": user.username, "password": user_password},
)
assert resp.status_code == 302
assert resp["location"] == "/foo"

View File

@@ -0,0 +1,83 @@
from __future__ import absolute_import
import json
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.test.utils import override_settings
from django.urls import reverse
from allauth.account import app_settings
from allauth.tests import TestCase
class AjaxTests(TestCase):
def _send_post_request(self, **kwargs):
return self.client.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": "john@example.org",
"email2": "john@example.org",
"password1": "johndoe",
"password2": "johndoe",
},
**kwargs,
)
def test_no_ajax_header(self):
resp = self._send_post_request()
self.assertEqual(302, resp.status_code)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
def test_ajax_header_x_requested_with(self):
resp = self._send_post_request(HTTP_X_REQUESTED_WITH="XMLHttpRequest")
self.assertEqual(200, resp.status_code)
self.assertEqual(settings.LOGIN_REDIRECT_URL, resp.json()["location"])
def test_ajax_header_http_accept(self):
resp = self._send_post_request(HTTP_ACCEPT="application/json")
self.assertEqual(200, resp.status_code)
self.assertEqual(settings.LOGIN_REDIRECT_URL, resp.json()["location"])
def test_ajax_password_reset(self):
get_user_model().objects.create(
username="john", email="john@example.org", is_active=True
)
resp = self.client.post(
reverse("account_reset_password"),
data={"email": "john@example.org"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ["john@example.org"])
self.assertEqual(resp["content-type"], "application/json")
def test_ajax_login_fail(self):
resp = self.client.post(
reverse("account_login"),
{},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp.status_code, 400)
json.loads(resp.content.decode("utf8"))
# TODO: Actually test something
@override_settings(
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.OPTIONAL
)
def test_ajax_login_success(self):
user = get_user_model().objects.create(username="john", is_active=True)
user.set_password("doe")
user.save()
resp = self.client.post(
reverse("account_login"),
{"login": "john", "password": "doe"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content.decode("utf8"))
self.assertEqual(data["location"], "/accounts/profile/")

View File

@@ -0,0 +1,75 @@
from __future__ import absolute_import
from django.contrib.auth import get_user_model
from django.test.utils import override_settings
from allauth.account import app_settings
from allauth.account.auth_backends import AuthenticationBackend
from allauth.tests import TestCase
class AuthenticationBackendTests(TestCase):
def setUp(self):
user = get_user_model().objects.create(
is_active=True, email="john@example.com", username="john"
)
user.set_password(user.username)
user.save()
self.user = user
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME
) # noqa
def test_auth_by_username(self):
user = self.user
backend = AuthenticationBackend()
self.assertEqual(
backend.authenticate(
request=None, username=user.username, password=user.username
).pk,
user.pk,
)
self.assertEqual(
backend.authenticate(
request=None, username=user.email, password=user.username
),
None,
)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL
) # noqa
def test_auth_by_email(self):
user = self.user
backend = AuthenticationBackend()
self.assertEqual(
backend.authenticate(
request=None, username=user.email, password=user.username
).pk,
user.pk,
)
self.assertEqual(
backend.authenticate(
request=None, username=user.username, password=user.username
),
None,
)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME_EMAIL
) # noqa
def test_auth_by_username_or_email(self):
user = self.user
backend = AuthenticationBackend()
self.assertEqual(
backend.authenticate(
request=None, username=user.email, password=user.username
).pk,
user.pk,
)
self.assertEqual(
backend.authenticate(
request=None, username=user.username, password=user.username
).pk,
user.pk,
)

View File

@@ -0,0 +1,304 @@
from __future__ import absolute_import
import json
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test.utils import override_settings
from django.urls import reverse
import pytest
from pytest_django.asserts import assertTemplateUsed
from allauth.account.models import EmailAddress, EmailConfirmationHMAC
from allauth.account.utils import user_email
from allauth.tests import TestCase
class ChangeEmailTests(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create(username="john", email="john1@example.org")
self.user.set_password("doe")
self.user.save()
self.email_address = EmailAddress.objects.create(
user=self.user, email=self.user.email, verified=True, primary=True
)
self.email_address2 = EmailAddress.objects.create(
user=self.user,
email="john2@example.org",
verified=False,
primary=False,
)
self.client.login(username="john", password="doe")
def test_ajax_get(self):
resp = self.client.get(
reverse("account_email"), HTTP_X_REQUESTED_WITH="XMLHttpRequest"
)
data = json.loads(resp.content.decode("utf8"))
assert data["data"] == [
{
"id": self.email_address.pk,
"email": "john1@example.org",
"primary": True,
"verified": True,
},
{
"id": self.email_address2.pk,
"email": "john2@example.org",
"primary": False,
"verified": False,
},
]
def test_ajax_add(self):
resp = self.client.post(
reverse("account_email"),
{"action_add": "", "email": "john3@example.org"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
data = json.loads(resp.content.decode("utf8"))
self.assertEqual(data["location"], reverse("account_email"))
def test_ajax_add_invalid(self):
resp = self.client.post(
reverse("account_email"),
{"action_add": "", "email": "john3#example.org"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
data = json.loads(resp.content.decode("utf8"))
assert "valid" in data["form"]["fields"]["email"]["errors"][0]
def test_remove_primary(self):
resp = self.client.post(
reverse("account_email"),
{"action_remove": "", "email": self.email_address.email},
)
EmailAddress.objects.get(pk=self.email_address.pk)
self.assertTemplateUsed(
resp, "account/messages/cannot_delete_primary_email.txt"
)
def test_ajax_remove_primary(self):
resp = self.client.post(
reverse("account_email"),
{"action_remove": "", "email": self.email_address.email},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertTemplateUsed(
resp, "account/messages/cannot_delete_primary_email.txt"
)
data = json.loads(resp.content.decode("utf8"))
self.assertEqual(data["location"], reverse("account_email"))
def test_remove_secondary(self):
resp = self.client.post(
reverse("account_email"),
{"action_remove": "", "email": self.email_address2.email},
)
self.assertRaises(
EmailAddress.DoesNotExist,
lambda: EmailAddress.objects.get(pk=self.email_address2.pk),
)
self.assertTemplateUsed(resp, "account/messages/email_deleted.txt")
def test_set_primary_unverified(self):
resp = self.client.post(
reverse("account_email"),
{"action_primary": "", "email": self.email_address2.email},
)
email_address = EmailAddress.objects.get(pk=self.email_address.pk)
email_address2 = EmailAddress.objects.get(pk=self.email_address2.pk)
self.assertFalse(email_address2.primary)
self.assertTrue(email_address.primary)
self.assertTemplateUsed(resp, "account/messages/unverified_primary_email.txt")
def test_set_primary(self):
email_address2 = EmailAddress.objects.get(pk=self.email_address2.pk)
email_address2.verified = True
email_address2.save()
resp = self.client.post(
reverse("account_email"),
{"action_primary": "", "email": self.email_address2.email},
)
email_address = EmailAddress.objects.get(pk=self.email_address.pk)
email_address2 = EmailAddress.objects.get(pk=self.email_address2.pk)
self.assertFalse(email_address.primary)
self.assertTrue(email_address2.primary)
self.assertTemplateUsed(resp, "account/messages/primary_email_set.txt")
def test_verify(self):
resp = self.client.post(
reverse("account_email"),
{"action_send": "", "email": self.email_address2.email},
)
self.assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt")
def test_verify_unknown_email(self):
assert EmailAddress.objects.filter(user=self.user).count() == 2
self.client.post(
reverse("account_email"),
{"action_send": "", "email": "email@unknown.org"},
)
# This unknown email address must not be implicitly added.
assert EmailAddress.objects.filter(user=self.user).count() == 2
@override_settings(ACCOUNT_MAX_EMAIL_ADDRESSES=2)
def test_add_with_two_limiter(self):
resp = self.client.post(
reverse("account_email"), {"action_add": "", "email": "john3@example.org"}
)
self.assertTemplateNotUsed(resp, "account/messages/email_confirmation_sent.txt")
@override_settings(ACCOUNT_MAX_EMAIL_ADDRESSES=None)
def test_add_with_none_limiter(self):
resp = self.client.post(
reverse("account_email"), {"action_add": "", "email": "john3@example.org"}
)
self.assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt")
@override_settings(ACCOUNT_MAX_EMAIL_ADDRESSES=0)
def test_add_with_zero_limiter(self):
resp = self.client.post(
reverse("account_email"), {"action_add": "", "email": "john3@example.org"}
)
self.assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt")
def test_set_email_as_primary_doesnt_override_existed_changes_on_the_user(self):
user = get_user_model().objects.create(
username="@raymond.penners", first_name="Before Update"
)
email = EmailAddress.objects.create(
user=user,
email="raymond.penners@example.com",
primary=True,
verified=True,
)
updated_first_name = "Updated"
get_user_model().objects.filter(id=user.id).update(
first_name=updated_first_name
)
email.set_as_primary()
user.refresh_from_db()
self.assertEqual(user.first_name, updated_first_name)
@override_settings(ACCOUNT_USER_MODEL_EMAIL_FIELD=None)
def test_set_email_as_primary_doesnt_override_existed_changes_on_the_user_for_user_model_without_email_field(
self,
):
self.test_set_email_as_primary_doesnt_override_existed_changes_on_the_user()
def test_delete_email_changes_user_email(user_factory, client, email_factory):
user = user_factory(email_verified=False)
client.force_login(user)
first_email = EmailAddress.objects.get(user=user)
first_email.primary = False
first_email.save()
# other_unverified_email
EmailAddress.objects.create(
user=user, email=email_factory(), verified=False, primary=False
)
other_verified_email = EmailAddress.objects.create(
user=user, email=email_factory(), verified=True, primary=False
)
assert user_email(user) == first_email.email
resp = client.post(
reverse("account_email"),
{"action_remove": "", "email": first_email.email},
)
assert resp.status_code == 302
user.refresh_from_db()
assert user_email(user) == other_verified_email.email
def test_delete_email_wipes_user_email(user_factory, client):
user = user_factory(email_verified=False)
client.force_login(user)
first_email = EmailAddress.objects.get(user=user)
first_email.primary = False
first_email.save()
assert user_email(user) == first_email.email
resp = client.post(
reverse("account_email"),
{"action_remove": "", "email": first_email.email},
)
assert resp.status_code == 302
user.refresh_from_db()
assert user_email(user) == ""
def test_change_email(user_factory, client, settings):
settings.ACCOUNT_CHANGE_EMAIL = True
settings.ACCOUNT_EMAIL_CONFIRMATION_HMAC = True
user = user_factory(email_verified=True)
client.force_login(user)
current_email = EmailAddress.objects.get(user=user)
resp = client.post(
reverse("account_email"),
{"action_add": "", "email": "change-to@this.org"},
)
assert resp.status_code == 302
new_email = EmailAddress.objects.get(email="change-to@this.org")
key = EmailConfirmationHMAC(new_email).key
with patch("allauth.account.signals.email_changed.send") as email_changed_mock:
resp = client.post(reverse("account_confirm_email", args=[key]))
assert resp.status_code == 302
assert not EmailAddress.objects.filter(pk=current_email.pk).exists()
assert EmailAddress.objects.filter(user=user).count() == 1
new_email.refresh_from_db()
assert new_email.verified
assert new_email.primary
assert email_changed_mock.called
def test_add(auth_client, user, settings):
resp = auth_client.post(
reverse("account_email"),
{"action_add": "", "email": "john3@example.org"},
)
EmailAddress.objects.get(
email="john3@example.org",
user=user,
verified=False,
primary=False,
)
assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt")
@pytest.mark.parametrize(
"prevent_enumeration",
[
False,
True,
"strict",
],
)
def test_add_not_allowed(
auth_client, user, settings, user_factory, prevent_enumeration
):
settings.ACCOUNT_PREVENT_ENUMERATION = prevent_enumeration
email = "inuse@byotheruser.com"
user_factory(email=email)
resp = auth_client.post(
reverse("account_email"),
{"action_add": "", "email": email},
)
if prevent_enumeration == "strict":
assert resp.status_code == 302
EmailAddress.objects.get(
email=email,
user=user,
verified=False,
primary=False,
)
assertTemplateUsed(resp, "account/messages/email_confirmation_sent.txt")
else:
assert resp.status_code == 200
assert resp.context["form"].errors == {
"email": ["A user is already registered with this email address."]
}

View File

@@ -0,0 +1,7 @@
from django.core.management import call_command
def test_unset_multipleprimaryemails(db):
# This command needs to be dropped, in favor of having a conditional
# constraint.
call_command("account_unsetmultipleprimaryemails")

View File

@@ -0,0 +1,365 @@
from __future__ import absolute_import
from datetime import timedelta
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.http import HttpResponseRedirect
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.timezone import now
from allauth.account import app_settings
from allauth.account.models import (
EmailAddress,
EmailConfirmation,
EmailConfirmationHMAC,
)
from allauth.account.signals import user_logged_in
from allauth.account.utils import user_pk_to_url_str
from allauth.tests import Mock, TestCase, patch
from .test_models import UUIDUser
@override_settings(
ACCOUNT_DEFAULT_HTTP_PROTOCOL="https",
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_SUBJECT_PREFIX=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_SIGNUP_REDIRECT_URL="/accounts/welcome/",
ACCOUNT_ADAPTER="allauth.account.adapter.DefaultAccountAdapter",
ACCOUNT_USERNAME_REQUIRED=True,
)
class ConfirmationViewTests(TestCase):
def _create_user(self, username="john", password="doe", **kwargs):
user = get_user_model().objects.create(
username=username, is_active=True, **kwargs
)
if password:
user.set_password(password)
else:
user.set_unusable_password()
user.save()
return user
@override_settings(
ACCOUNT_EMAIL_CONFIRMATION_HMAC=True,
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=True,
)
def test_login_on_confirm(self):
user = self._create_user()
email = EmailAddress.objects.create(
user=user, email="a@b.com", verified=False, primary=True
)
key = EmailConfirmationHMAC(email).key
receiver_mock = Mock() # we've logged if signal was called
user_logged_in.connect(receiver_mock)
# fake post-signup account_user stash
session = self.client.session
session["account_user"] = user_pk_to_url_str(user)
session.save()
resp = self.client.post(reverse("account_confirm_email", args=[key]))
email = EmailAddress.objects.get(pk=email.pk)
self.assertTrue(email.verified)
receiver_mock.assert_called_once_with(
sender=get_user_model(),
request=resp.wsgi_request,
response=resp,
user=get_user_model().objects.get(username="john"),
signal=user_logged_in,
)
user_logged_in.disconnect(receiver_mock)
@override_settings(
ACCOUNT_EMAIL_CONFIRMATION_HMAC=True,
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=True,
)
@patch("allauth.account.views.perform_login")
@patch("allauth.account.utils.get_user_model", return_value=UUIDUser)
def test_login_on_confirm_uuid_user(self, mocked_gum, mock_perform_login):
user = UUIDUser(is_active=True, email="john@example.com", username="john")
# fake post-signup account_user stash
session = self.client.session
session["account_user"] = user_pk_to_url_str(user)
session.save()
# fake email and email confirmation to avoid swappable model hell
email = Mock(verified=False, user=user)
key = "mockkey"
confirmation = Mock(autospec=EmailConfirmationHMAC, key=key)
confirmation.email_address = email
confirmation.from_key.return_value = confirmation
mock_perform_login.return_value = HttpResponseRedirect(redirect_to="/")
with patch("allauth.account.views.EmailConfirmationHMAC", confirmation):
self.client.post(reverse("account_confirm_email", args=[key]))
assert mock_perform_login.called
@override_settings(
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False,
)
def test_email_verification_failed(self):
verified_user = get_user_model().objects.create(username="foobar")
unverified_user = get_user_model().objects.create(username="foobar2")
EmailAddress.objects.create(
user=verified_user,
email="foo@bar.org",
verified=True,
primary=True,
)
email_address = EmailAddress.objects.create(
user=unverified_user,
email="foo@bar.org",
verified=False,
primary=False,
)
confirmation = EmailConfirmation.objects.create(
email_address=email_address,
key="dummy",
sent=now(),
)
c = Client()
resp = c.post(reverse("account_confirm_email", args=[confirmation.key]))
self.assertTemplateUsed(resp, "account/messages/email_confirmation_failed.txt")
@override_settings(
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False, ACCOUNT_EMAIL_CONFIRMATION_COOLDOWN=10
)
def test_email_verification_mandatory(self):
c = Client()
# Signup
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": "john@example.com",
"password1": "johndoe",
"password2": "johndoe",
},
follow=True,
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(mail.outbox[0].to, ["john@example.com"])
self.assertGreater(mail.outbox[0].body.find("https://"), 0)
self.assertEqual(len(mail.outbox), 1)
self.assertTemplateUsed(
resp,
"account/verification_sent.%s" % app_settings.TEMPLATE_EXTENSION,
)
# Attempt to login, unverified
for attempt in [1, 2]:
resp = c.post(
reverse("account_login"),
{"login": "johndoe", "password": "johndoe"},
follow=True,
)
# is_active is controlled by the admin to manually disable
# users. I don't want this flag to flip automatically whenever
# users verify their email addresses.
self.assertTrue(
get_user_model()
.objects.filter(username="johndoe", is_active=True)
.exists()
)
self.assertTemplateUsed(
resp,
"account/verification_sent." + app_settings.TEMPLATE_EXTENSION,
)
# Attempt 1: no mail is sent due to cool-down ,
# but there was already a mail in the outbox.
self.assertEqual(len(mail.outbox), attempt)
self.assertEqual(
EmailConfirmation.objects.filter(
email_address__email="john@example.com"
).count(),
attempt,
)
# Wait for cooldown
EmailConfirmation.objects.update(sent=now() - timedelta(days=1))
# Verify, and re-attempt to login.
confirmation = EmailConfirmation.objects.filter(
email_address__user__username="johndoe"
)[:1].get()
resp = c.get(reverse("account_confirm_email", args=[confirmation.key]))
self.assertTemplateUsed(
resp, "account/email_confirm.%s" % app_settings.TEMPLATE_EXTENSION
)
c.post(reverse("account_confirm_email", args=[confirmation.key]))
resp = c.post(
reverse("account_login"),
{"login": "johndoe", "password": "johndoe"},
)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
@override_settings(
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.OPTIONAL,
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE=False,
)
def test_optional_email_verification(self):
c = Client()
# Signup
c.get(reverse("account_signup"))
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": "john@example.com",
"password1": "johndoe",
},
)
# Logged in
self.assertRedirects(
resp, settings.ACCOUNT_SIGNUP_REDIRECT_URL, fetch_redirect_response=False
)
self.assertEqual(mail.outbox[0].to, ["john@example.com"])
self.assertEqual(len(mail.outbox), 1)
# Logout & login again
c.logout()
# Wait for cooldown
EmailConfirmation.objects.update(sent=now() - timedelta(days=1))
# Signup
resp = c.post(
reverse("account_login"),
{"login": "johndoe", "password": "johndoe"},
)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
self.assertEqual(mail.outbox[0].to, ["john@example.com"])
# There was an issue that we sent out email confirmation mails
# on each login in case of optional verification. Make sure
# this is not the case:
self.assertEqual(len(mail.outbox), 1)
@override_settings(ACCOUNT_EMAIL_CONFIRMATION_HMAC=True)
def test_email_confirmation_hmac_falls_back(self):
user = self._create_user()
email = EmailAddress.objects.create(
user=user, email="a@b.com", verified=False, primary=True
)
confirmation = EmailConfirmation.create(email)
confirmation.sent = now()
confirmation.save()
self.client.post(reverse("account_confirm_email", args=[confirmation.key]))
email = EmailAddress.objects.get(pk=email.pk)
self.assertTrue(email.verified)
@override_settings(ACCOUNT_EMAIL_CONFIRMATION_HMAC=True)
def test_email_confirmation_hmac(self):
user = self._create_user()
email = EmailAddress.objects.create(
user=user, email="a@b.com", verified=False, primary=True
)
confirmation = EmailConfirmationHMAC(email)
request = RequestFactory().get("/")
confirmation.send(request=request)
self.assertEqual(len(mail.outbox), 1)
self.client.post(reverse("account_confirm_email", args=[confirmation.key]))
email = EmailAddress.objects.get(pk=email.pk)
self.assertTrue(email.verified)
@override_settings(
ACCOUNT_EMAIL_CONFIRMATION_HMAC=True,
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS=0,
)
def test_email_confirmation_hmac_timeout(self):
user = self._create_user()
email = EmailAddress.objects.create(
user=user, email="a@b.com", verified=False, primary=True
)
confirmation = EmailConfirmationHMAC(email)
request = RequestFactory().get("/")
confirmation.send(request=request)
self.assertEqual(len(mail.outbox), 1)
self.client.post(reverse("account_confirm_email", args=[confirmation.key]))
email = EmailAddress.objects.get(pk=email.pk)
self.assertFalse(email.verified)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL
)
def test_confirm_email_with_another_user_logged_in(self):
"""Test the email confirmation view. If User B clicks on an email
verification link while logged in as User A, ensure User A gets
logged out."""
user = get_user_model().objects.create_user(
username="john", email="john@example.org", password="doe"
)
self.client.force_login(user)
self.client.post(
reverse("account_email"), {"email": user.email, "action_send": ""}
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, [user.email])
self.client.logout()
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
user2 = self._create_user(username="john2", email="john2@example.com")
EmailAddress.objects.create(
user=user2, email=user2.email, primary=True, verified=True
)
resp = self.client.post(
reverse("account_login"),
{
"login": user2.email,
"password": "doe",
},
)
self.assertEqual(user2, resp.context["user"])
url = body[body.find("/confirm-email/") :].split()[0]
resp = self.client.post(url)
self.assertTemplateUsed(resp, "account/messages/logged_out.txt")
self.assertTemplateUsed(resp, "account/messages/email_confirmed.txt")
self.assertRedirects(resp, settings.LOGIN_URL, fetch_redirect_response=False)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL
)
def test_confirm_email_with_same_user_logged_in(self):
"""Test the email confirmation view. If User A clicks on an email
verification link while logged in, ensure the user
stayed logged in."""
user = get_user_model().objects.create_user(
username="john", email="john@example.org", password="doe"
)
self.client.force_login(user)
self.client.post(
reverse("account_email"), {"email": user.email, "action_send": ""}
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, [user.email])
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
url = body[body.find("/confirm-email/") :].split()[0]
resp = self.client.post(url)
self.assertTemplateNotUsed(resp, "account/messages/logged_out.txt")
self.assertTemplateUsed(resp, "account/messages/email_confirmed.txt")
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
self.assertEqual(user, resp.wsgi_request.user)

View File

@@ -0,0 +1,314 @@
from __future__ import absolute_import
import json
import django
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.test.utils import override_settings
from django.urls import reverse
from allauth.account import app_settings
from allauth.account.models import EmailAddress
from allauth.tests import TestCase
@override_settings(
ACCOUNT_DEFAULT_HTTP_PROTOCOL="https",
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_SUBJECT_PREFIX=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_SIGNUP_REDIRECT_URL="/accounts/welcome/",
ACCOUNT_ADAPTER="allauth.account.adapter.DefaultAccountAdapter",
ACCOUNT_USERNAME_REQUIRED=True,
)
class LoginTests(TestCase):
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME_EMAIL
)
def test_username_containing_at(self):
user = get_user_model().objects.create(username="@raymond.penners")
user.set_password("psst")
user.save()
EmailAddress.objects.create(
user=user,
email="raymond.penners@example.com",
primary=True,
verified=True,
)
resp = self.client.post(
reverse("account_login"),
{"login": "@raymond.penners", "password": "psst"},
)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
def _create_user(self, username="john", password="doe", **kwargs):
user = get_user_model().objects.create(
username=username, is_active=True, **kwargs
)
if password:
user.set_password(password)
else:
user.set_unusable_password()
user.save()
return user
def _create_user_and_login(self, usable_password=True):
password = "doe" if usable_password else False
user = self._create_user(password=password)
self.client.force_login(user)
return user
def test_redirect_when_authenticated(self):
self._create_user_and_login()
c = self.client
resp = c.get(reverse("account_login"))
self.assertRedirects(resp, "/accounts/profile/", fetch_redirect_response=False)
def test_ajax_password_change(self):
self._create_user_and_login()
resp = self.client.post(
reverse("account_change_password"),
data={
"oldpassword": "doe",
"password1": "AbCdEf!123",
"password2": "AbCdEf!123456",
},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp["content-type"], "application/json")
data = json.loads(resp.content.decode("utf8"))
assert "same password" in data["form"]["fields"]["password2"]["errors"][0]
@override_settings(
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.OPTIONAL
)
def test_login_unverified_account_optional(self):
"""Tests login behavior when email verification is optional."""
user = get_user_model().objects.create(username="john")
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="user@example.com", primary=True, verified=False
)
resp = self.client.post(
reverse("account_login"), {"login": "john", "password": "doe"}
)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
@override_settings(
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.OPTIONAL,
ACCOUNT_LOGIN_ATTEMPTS_LIMIT=3,
)
def test_login_failed_attempts_exceeded(self):
user = get_user_model().objects.create(username="john")
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="user@example.com", primary=True, verified=False
)
for i in range(5):
is_valid_attempt = i == 4
is_locked = i >= 3
resp = self.client.post(
reverse("account_login"),
{
"login": ["john", "John", "JOHN", "JOhn", "joHN"][i],
"password": ("doe" if is_valid_attempt else "wrong"),
},
)
if django.VERSION >= (4, 1):
self.assertFormError(
resp.context["form"],
None,
"Too many failed login attempts. Try again later."
if is_locked
else "The username and/or password you specified are not correct.",
)
else:
self.assertFormError(
resp,
"form",
None,
"Too many failed login attempts. Try again later."
if is_locked
else "The username and/or password you specified are not correct.",
)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL,
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_LOGIN_ATTEMPTS_LIMIT=1,
)
def test_login_failed_attempts_exceeded_cleared_on_password_reset(self):
# Ensure that login attempts, once they hit the limit,
# can use the password reset mechanism to regain access.
user = get_user_model().objects.create(
username="john", email="john@example.org", is_active=True
)
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="john@example.org", primary=True, verified=True
)
resp = self.client.post(
reverse("account_login"), {"login": user.email, "password": "bad"}
)
if django.VERSION >= (4, 1):
self.assertFormError(
resp.context["form"],
None,
"The email address and/or password you specified are not correct.",
)
else:
self.assertFormError(
resp,
"form",
None,
"The email address and/or password you specified are not correct.",
)
resp = self.client.post(
reverse("account_login"), {"login": user.email, "password": "bad"}
)
if django.VERSION >= (4, 1):
self.assertFormError(
resp.context["form"],
None,
"Too many failed login attempts. Try again later.",
)
else:
self.assertFormError(
resp,
"form",
None,
"Too many failed login attempts. Try again later.",
)
self.client.post(reverse("account_reset_password"), data={"email": user.email})
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
# Extract URL for `password_reset_from_key` view and access it
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
# Follow the redirect the actual password reset page with the key
# hidden.
url = resp.url
resp = self.client.get(url)
self.assertTemplateUsed(
resp,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertFalse("token_fail" in resp.context_data)
new_password = "newpass123"
# Reset the password
resp = self.client.post(
url, {"password1": new_password, "password2": new_password}
)
self.assertRedirects(resp, reverse("account_reset_password_from_key_done"))
# Check the new password is in effect
user = get_user_model().objects.get(pk=user.pk)
self.assertTrue(user.check_password(new_password))
resp = self.client.post(
reverse("account_login"),
{"login": user.email, "password": new_password},
)
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL,
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_LOGIN_ATTEMPTS_LIMIT=1,
)
def test_login_using_unverified_email_address_is_prohibited(self):
user = get_user_model().objects.create(
username="john", email="john@example.org", is_active=True
)
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="john@example.org", primary=True, verified=True
)
EmailAddress.objects.create(
user=user, email="john@example.com", primary=True, verified=False
)
resp = self.client.post(
reverse("account_login"), {"login": "john@example.com", "password": "doe"}
)
self.assertRedirects(
resp,
reverse("account_email_verification_sent"),
fetch_redirect_response=False,
)
self.assertEqual(len(mail.outbox), 1)
assert mail.outbox[0].to == ["john@example.com"]
def test_login_unverified_account_mandatory(self):
"""Tests login behavior when email verification is mandatory."""
user = get_user_model().objects.create(username="john")
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="user@example.com", primary=True, verified=False
)
resp = self.client.post(
reverse("account_login"), {"login": "john", "password": "doe"}
)
self.assertRedirects(resp, reverse("account_email_verification_sent"))
def test_login_inactive_account(self):
"""
Tests login behavior with inactive accounts.
Inactive user accounts should be prevented from performing any actions,
regardless of their verified state.
"""
# Inactive and verified user account
user = get_user_model().objects.create(username="john", is_active=False)
user.set_password("doe")
user.save()
EmailAddress.objects.create(
user=user, email="john@example.com", primary=True, verified=True
)
resp = self.client.post(
reverse("account_login"), {"login": "john", "password": "doe"}
)
self.assertRedirects(resp, reverse("account_inactive"))
# Inactive and unverified user account
user = get_user_model().objects.create(username="doe", is_active=False)
user.set_password("john")
user.save()
EmailAddress.objects.create(
user=user, email="user@example.com", primary=True, verified=False
)
resp = self.client.post(
reverse("account_login"), {"login": "doe", "password": "john"}
)
self.assertRedirects(resp, reverse("account_inactive"))
@override_settings(ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS=False)
def test_account_authenticated_login_redirects_is_false(self):
self._create_user_and_login()
resp = self.client.get(reverse("account_login"))
self.assertEqual(resp.status_code, 200)

View File

@@ -0,0 +1,65 @@
from __future__ import absolute_import
from django.contrib.auth import get_user_model
from django.core import validators
from django.test.client import Client
from django.test.utils import override_settings
from django.urls import reverse
from allauth.account import app_settings
from allauth.account.signals import user_logged_out
from allauth.tests import Mock, TestCase
test_username_validators = [
validators.RegexValidator(regex=r"^[a-c]+$", message="not abc", flags=0)
]
@override_settings(
ACCOUNT_DEFAULT_HTTP_PROTOCOL="https",
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_SUBJECT_PREFIX=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_SIGNUP_REDIRECT_URL="/accounts/welcome/",
ACCOUNT_ADAPTER="allauth.account.adapter.DefaultAccountAdapter",
ACCOUNT_USERNAME_REQUIRED=True,
)
class LogoutTests(TestCase):
@override_settings(ACCOUNT_LOGOUT_ON_GET=True)
def test_logout_view_on_get(self):
c, resp = self._logout_view("get")
self.assertTemplateUsed(resp, "account/messages/logged_out.txt")
@override_settings(ACCOUNT_LOGOUT_ON_GET=False)
def test_logout_view_on_post(self):
c, resp = self._logout_view("get")
self.assertTemplateUsed(
resp, "account/logout.%s" % app_settings.TEMPLATE_EXTENSION
)
receiver_mock = Mock()
user_logged_out.connect(receiver_mock)
resp = c.post(reverse("account_logout"))
self.assertTemplateUsed(resp, "account/messages/logged_out.txt")
receiver_mock.assert_called_once_with(
sender=get_user_model(),
request=resp.wsgi_request,
user=get_user_model().objects.get(username="john"),
signal=user_logged_out,
)
user_logged_out.disconnect(receiver_mock)
def _logout_view(self, method):
c = Client()
user = get_user_model().objects.create(username="john", is_active=True)
user.set_password("doe")
user.save()
c = Client()
c.login(username="john", password="doe")
return c, getattr(c, method)(reverse("account_logout"))

View File

@@ -0,0 +1,25 @@
from django.conf import settings
from django.http import HttpResponse
import pytest
from allauth.account.middleware import AccountMiddleware
@pytest.mark.parametrize(
"path,status_code,login_removed",
[
("/", 200, True),
("/", 404, False),
(settings.STATIC_URL, 200, False),
("/favicon.ico", 200, False),
("/robots.txt", 200, False),
("/humans.txt", 200, False),
],
)
def test_remove_dangling_login(rf, path, status_code, login_removed):
request = rf.get(path)
request.session = {"account_login": True}
mw = AccountMiddleware(lambda request: HttpResponse(status=status_code))
mw(request)
assert ("account_login" in request.session) is (not login_removed)

View File

@@ -0,0 +1,27 @@
import uuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from allauth.account.models import EmailAddress
class UUIDUser(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class Meta(AbstractUser.Meta):
swappable = "AUTH_USER_MODEL"
def test_add_new_email(rf, user, settings):
settings.ACCOUNT_CHANGE_EMAIL = True
request = rf.get("/")
assert EmailAddress.objects.filter(user=user).count() == 1
new_email = EmailAddress.objects.add_new_email(request, user, "new@email.org")
assert not new_email.verified
assert not new_email.primary
assert EmailAddress.objects.filter(user=user).count() == 2
EmailAddress.objects.add_new_email(request, user, "new2@email.org")
assert EmailAddress.objects.filter(user=user).count() == 2
new_email.refresh_from_db()
assert new_email.email == "new2@email.org"

View File

@@ -0,0 +1,28 @@
from __future__ import absolute_import
from django.contrib.auth import get_user_model
from django.test.utils import override_settings
from django.urls import reverse
from allauth.tests import TestCase
@override_settings(
CACHES={
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
},
},
ACCOUNT_RATE_LIMITS={"reset_password_email": "1/m"},
)
class RateLimitTests(TestCase):
def test_case_insensitive_password_reset(self):
get_user_model().objects.create(email="a@b.com")
resp = self.client.post(
reverse("account_reset_password"), data={"email": "a@b.com"}
)
assert resp.status_code == 302
resp = self.client.post(
reverse("account_reset_password"), data={"email": "A@B.COM"}
)
assert resp.status_code == 429

View File

@@ -0,0 +1,302 @@
from __future__ import absolute_import
import json
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core import mail
from django.test.utils import override_settings
from django.urls import reverse
from allauth.account import app_settings
from allauth.account.forms import ResetPasswordForm
from allauth.account.models import EmailAddress
from allauth.tests import TestCase
@override_settings(
ACCOUNT_PREVENT_ENUMERATION=False,
ACCOUNT_DEFAULT_HTTP_PROTOCOL="https",
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_SUBJECT_PREFIX=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_SIGNUP_REDIRECT_URL="/accounts/welcome/",
ACCOUNT_ADAPTER="allauth.account.adapter.DefaultAccountAdapter",
ACCOUNT_USERNAME_REQUIRED=True,
)
class ResetPasswordTests(TestCase):
def test_user_email_not_sent_inactive_user(self):
User = get_user_model()
User.objects.create_user(
"mike123", "mike@ixample.org", "test123", is_active=False
)
data = {"email": "mike@ixample.org"}
form = ResetPasswordForm(data)
self.assertFalse(form.is_valid())
def test_password_reset_get(self):
resp = self.client.get(reverse("account_reset_password"))
self.assertTemplateUsed(resp, "account/password_reset.html")
def test_password_set_redirect(self):
resp = self._password_set_or_change_redirect("account_set_password", True)
self.assertRedirects(
resp,
reverse("account_change_password"),
fetch_redirect_response=False,
)
def test_set_password_not_allowed(self):
user = self._create_user_and_login(True)
pwd = "!*123i1uwn12W23"
self.assertFalse(user.check_password(pwd))
resp = self.client.post(
reverse("account_set_password"),
data={"password1": pwd, "password2": pwd},
)
user.refresh_from_db()
self.assertFalse(user.check_password(pwd))
self.assertTrue(user.has_usable_password())
self.assertEqual(resp.status_code, 302)
def test_password_change_no_redirect(self):
resp = self._password_set_or_change_redirect("account_change_password", True)
self.assertEqual(resp.status_code, 200)
def test_password_set_no_redirect(self):
resp = self._password_set_or_change_redirect("account_set_password", False)
self.assertEqual(resp.status_code, 200)
def test_password_change_redirect(self):
resp = self._password_set_or_change_redirect("account_change_password", False)
self.assertRedirects(
resp,
reverse("account_set_password"),
fetch_redirect_response=False,
)
def test_password_forgotten_username_hint(self):
user = self._request_new_password()
body = mail.outbox[0].body
assert user.username in body
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL
)
def test_password_forgotten_no_username_hint(self):
user = self._request_new_password()
body = mail.outbox[0].body
assert user.username not in body
def _request_new_password(self):
user = get_user_model().objects.create(
username="john", email="john@example.org", is_active=True
)
user.set_password("doe")
user.save()
self.client.post(
reverse("account_reset_password"),
data={"email": "john@example.org"},
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ["john@example.org"])
return user
def test_password_reset_flow_with_empty_session(self):
"""
Test the password reset flow when the session is empty:
requesting a new password, receiving the reset link via email,
following the link, getting redirected to the
new link (without the token)
Copying the link and using it in a DIFFERENT client (Browser/Device).
"""
# Request new password
self._request_new_password()
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
# Extract URL for `password_reset_from_key` view
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
reset_pass_url = resp.url
# Accessing the url via a different session
resp = self.client_class().get(reset_pass_url)
# We should receive the token_fail context_data
self.assertTemplateUsed(
resp,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertTrue(resp.context_data["token_fail"])
def test_password_reset_flow(self):
"""
Tests the password reset flow: requesting a new password,
receiving the reset link via email and finally resetting the
password to a new value.
"""
# Request new password
user = self._request_new_password()
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
# Extract URL for `password_reset_from_key` view and access it
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
# Follow the redirect the actual password reset page with the key
# hidden.
url = resp.url
resp = self.client.get(url)
self.assertTemplateUsed(
resp,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertFalse("token_fail" in resp.context_data)
# Reset the password
resp = self.client.post(
url, {"password1": "newpass123", "password2": "newpass123"}
)
self.assertRedirects(resp, reverse("account_reset_password_from_key_done"))
# Check the new password is in effect
user = get_user_model().objects.get(pk=user.pk)
self.assertTrue(user.check_password("newpass123"))
# Trying to reset the password against the same URL (or any other
# invalid/obsolete URL) returns a bad token response
resp = self.client.post(
url, {"password1": "newpass123", "password2": "newpass123"}
)
self.assertTemplateUsed(
resp,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertTrue(resp.context_data["token_fail"])
# Same should happen when accessing the page directly
response = self.client.get(url)
self.assertTemplateUsed(
response,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertTrue(response.context_data["token_fail"])
# When in XHR views, it should respond with a 400 bad request
# code, and the response body should contain the JSON-encoded
# error from the adapter
response = self.client.post(
url,
{"password1": "newpass123", "password2": "newpass123"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 400)
data = json.loads(response.content.decode("utf8"))
assert "invalid" in data["form"]["errors"][0]
@override_settings(
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.EMAIL
)
def test_password_reset_flow_with_another_user_logged_in(self):
"""
Tests the password reset flow: if User B requested a password
reset earlier and now User A is logged in, User B now clicks on
the link, ensure User A is logged out before continuing.
"""
# Request new password
self._request_new_password()
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
user2 = self._create_user(username="john2", email="john2@example.com")
EmailAddress.objects.create(
user=user2, email=user2.email, primary=True, verified=True
)
resp = self.client.post(
reverse("account_login"),
{
"login": user2.email,
"password": "doe",
},
)
self.assertEqual(user2, resp.context["user"])
# Extract URL for `password_reset_from_key` view and access it
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
# Follow the redirect the actual password reset page with the key
# hidden.
url = resp.url
resp = self.client.get(url)
self.assertTemplateUsed(
resp, "account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION
)
self.assertFalse("token_fail" in resp.context_data)
# Reset the password
resp = self.client.post(
url, {"password1": "newpass123", "password2": "newpass123"}, follow=True
)
self.assertRedirects(resp, reverse("account_reset_password_from_key_done"))
self.assertNotEqual(user2, resp.context["user"])
self.assertEqual(AnonymousUser(), resp.context["user"])
def test_password_reset_flow_with_email_changed(self):
"""
Test that the password reset token is invalidated if
the user email address was changed.
"""
user = self._request_new_password()
body = mail.outbox[0].body
self.assertGreater(body.find("https://"), 0)
EmailAddress.objects.create(user=user, email="other@email.org")
# Extract URL for `password_reset_from_key` view
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
self.assertTemplateUsed(
resp,
"account/password_reset_from_key.%s" % app_settings.TEMPLATE_EXTENSION,
)
self.assertTrue("token_fail" in resp.context_data)
@override_settings(ACCOUNT_LOGIN_ON_PASSWORD_RESET=True)
def test_password_reset_ACCOUNT_LOGIN_ON_PASSWORD_RESET(self):
user = self._request_new_password()
body = mail.outbox[0].body
url = body[body.find("/password/reset/") :].split()[0]
resp = self.client.get(url)
# Follow the redirect the actual password reset page with the key
# hidden.
resp = self.client.post(
resp.url, {"password1": "newpass123", "password2": "newpass123"}
)
self.assertTrue(user.is_authenticated)
# EmailVerificationMethod.MANDATORY sends us to the confirm-email page
self.assertRedirects(resp, "/confirm-email/")
def _create_user(self, username="john", password="doe", **kwargs):
user = get_user_model().objects.create(
username=username, is_active=True, **kwargs
)
if password:
user.set_password(password)
else:
user.set_unusable_password()
user.save()
return user
def _create_user_and_login(self, usable_password=True):
password = "doe" if usable_password else False
user = self._create_user(password=password)
self.client.force_login(user)
return user
def _password_set_or_change_redirect(self, urlname, usable_password):
self._create_user_and_login(usable_password)
return self.client.get(reverse(urlname))

View File

@@ -0,0 +1,50 @@
from __future__ import absolute_import
from django.contrib.auth import get_user_model
from django.core import mail
from django.test.client import RequestFactory
from django.test.utils import override_settings
from allauth.account.forms import ResetPasswordForm
from allauth.tests import TestCase
@override_settings(ACCOUNT_PREVENT_ENUMERATION=False)
class TestCVE2019_19844(TestCase):
global_request = RequestFactory().get("/")
def test_user_email_unicode_collision(self):
User = get_user_model()
User.objects.create_user("mike123", "mike@example.org", "test123")
User.objects.create_user("mike456", "mıke@example.org", "test123")
data = {"email": "mıke@example.org"}
form = ResetPasswordForm(data)
self.assertTrue(form.is_valid())
form.save(self.global_request)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ["mıke@example.org"])
def test_user_email_domain_unicode_collision(self):
User = get_user_model()
User.objects.create_user("mike123", "mike@ixample.org", "test123")
User.objects.create_user("mike456", "mike@ıxample.org", "test123")
data = {"email": "mike@ıxample.org"}
form = ResetPasswordForm(data)
self.assertTrue(form.is_valid())
form.save(self.global_request)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ["mike@ıxample.org"])
def test_user_email_unicode_collision_nonexistent(self):
User = get_user_model()
User.objects.create_user("mike123", "mike@example.org", "test123")
data = {"email": "mıke@example.org"}
form = ResetPasswordForm(data)
self.assertFalse(form.is_valid())
def test_user_email_domain_unicode_collision_nonexistent(self):
User = get_user_model()
User.objects.create_user("mike123", "mike@ixample.org", "test123")
data = {"email": "mike@ıxample.org"}
form = ResetPasswordForm(data)
self.assertFalse(form.is_valid())

View File

@@ -0,0 +1,388 @@
from __future__ import absolute_import
import django
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
import pytest
from pytest_django.asserts import assertTemplateUsed
from allauth.account import app_settings
from allauth.account.adapter import get_adapter
from allauth.account.forms import BaseSignupForm, SignupForm
from allauth.account.models import EmailAddress
from allauth.core import context
from allauth.tests import TestCase
from allauth.utils import get_username_max_length
class CustomSignupFormTests(TestCase):
@override_settings(
ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE=True,
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE=True,
)
def test_custom_form_field_order(self):
expected_field_order = [
"email",
"email2",
"password1",
"password2",
"username",
"last_name",
"first_name",
]
class TestSignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
field_order = expected_field_order
class CustomSignupForm(SignupForm, TestSignupForm):
# ACCOUNT_SIGNUP_FORM_CLASS is only abided by when the
# BaseSignupForm definition is loaded the first time on Django
# startup. @override_settings() has therefore no effect.
pass
form = CustomSignupForm()
self.assertEqual(list(form.fields.keys()), expected_field_order)
def test_user_class_attribute(self):
from django.contrib.auth import get_user_model
from django.db.models.query_utils import DeferredAttribute
class CustomSignupForm(SignupForm):
# ACCOUNT_SIGNUP_FORM_CLASS is only abided by when the
# BaseSignupForm definition is loaded the first time on Django
# startup. @override_settings() has therefore no effect.
pass
User = get_user_model()
data = {
"username": "username",
"email": "user@example.com",
"password1": "very-secret",
"password2": "very-secret",
}
form = CustomSignupForm(data, email_required=True)
assert isinstance(User.username, DeferredAttribute)
form.is_valid()
assert isinstance(User.username, DeferredAttribute)
class BaseSignupFormTests(TestCase):
@override_settings(
ACCOUNT_USERNAME_REQUIRED=True, ACCOUNT_USERNAME_BLACKLIST=["username"]
)
def test_username_in_blacklist(self):
data = {
"username": "username",
"email": "user@example.com",
}
form = BaseSignupForm(data, email_required=True)
self.assertFalse(form.is_valid())
@override_settings(
ACCOUNT_USERNAME_REQUIRED=True, ACCOUNT_USERNAME_BLACKLIST=["username"]
)
def test_username_not_in_blacklist(self):
data = {
"username": "theusername",
"email": "user@example.com",
}
form = BaseSignupForm(data, email_required=True)
self.assertTrue(form.is_valid())
@override_settings(ACCOUNT_USERNAME_REQUIRED=True)
def test_username_maxlength(self):
data = {
"username": "username",
"email": "user@example.com",
}
form = BaseSignupForm(data, email_required=True)
max_length = get_username_max_length()
field = form.fields["username"]
self.assertEqual(field.max_length, max_length)
widget = field.widget
self.assertEqual(widget.attrs.get("maxlength"), str(max_length))
@override_settings(
ACCOUNT_USERNAME_REQUIRED=True, ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE=True
)
def test_signup_email_verification(self):
data = {
"username": "username",
"email": "user@example.com",
}
form = BaseSignupForm(data, email_required=True)
self.assertFalse(form.is_valid())
data = {
"username": "username",
"email": "user@example.com",
"email2": "user@example.com",
}
form = BaseSignupForm(data, email_required=True)
self.assertTrue(form.is_valid())
data["email2"] = "anotheruser@example.com"
form = BaseSignupForm(data, email_required=True)
self.assertFalse(form.is_valid())
@override_settings(
ACCOUNT_DEFAULT_HTTP_PROTOCOL="https",
ACCOUNT_EMAIL_VERIFICATION=app_settings.EmailVerificationMethod.MANDATORY,
ACCOUNT_AUTHENTICATION_METHOD=app_settings.AuthenticationMethod.USERNAME,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_SUBJECT_PREFIX=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_SIGNUP_REDIRECT_URL="/accounts/welcome/",
ACCOUNT_ADAPTER="allauth.account.adapter.DefaultAccountAdapter",
ACCOUNT_USERNAME_REQUIRED=True,
)
class SignupTests(TestCase):
def test_signup_same_email_verified_externally(self):
user = self._test_signup_email_verified_externally(
"john@example.com", "john@example.com"
)
self.assertEqual(EmailAddress.objects.filter(user=user).count(), 1)
EmailAddress.objects.get(
verified=True, email="john@example.com", user=user, primary=True
)
def test_signup_other_email_verified_externally(self):
"""
John is invited on john@example.org, but signs up via john@example.com.
Email verification is by-passed, their home email address is
used as a secondary.
"""
user = self._test_signup_email_verified_externally(
"john@example.com", "john@example.org"
)
self.assertEqual(EmailAddress.objects.filter(user=user).count(), 2)
EmailAddress.objects.get(
verified=False, email="john@example.com", user=user, primary=False
)
EmailAddress.objects.get(
verified=True, email="john@example.org", user=user, primary=True
)
def _test_signup_email_verified_externally(self, signup_email, verified_email):
username = "johndoe"
request = RequestFactory().post(
reverse("account_signup"),
{
"username": username,
"email": signup_email,
"password1": "johndoe",
"password2": "johndoe",
},
)
# Fake stash_verified_email
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
request.user = AnonymousUser()
request.session["account_verified_email"] = verified_email
from allauth.account.views import signup
with context.request_context(request):
resp = signup(request)
self.assertEqual(resp.status_code, 302)
self.assertEqual(
resp["location"], get_adapter().get_signup_redirect_url(request)
)
self.assertEqual(len(mail.outbox), 0)
return get_user_model().objects.get(username=username)
@override_settings(
ACCOUNT_USERNAME_REQUIRED=True,
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE=True,
)
def test_signup_password_twice_form_error(self):
resp = self.client.post(
reverse("account_signup"),
data={
"username": "johndoe",
"email": "john@example.org",
"password1": "johndoe",
"password2": "janedoe",
},
)
if django.VERSION >= (4, 1):
self.assertFormError(
resp.context["form"],
"password2",
"You must type the same password each time.",
)
else:
self.assertFormError(
resp,
"form",
"password2",
"You must type the same password each time.",
)
@override_settings(
ACCOUNT_USERNAME_REQUIRED=True, ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE=True
)
def test_signup_email_twice(self):
request = RequestFactory().post(
reverse("account_signup"),
{
"username": "johndoe",
"email": "john@example.org",
"email2": "john@example.org",
"password1": "johndoe",
"password2": "johndoe",
},
)
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
request.user = AnonymousUser()
from allauth.account.views import signup
with context.request_context(request):
signup(request)
user = get_user_model().objects.get(username="johndoe")
self.assertEqual(user.email, "john@example.org")
@override_settings(
AUTH_PASSWORD_VALIDATORS=[
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {
"min_length": 9,
},
}
]
)
def test_django_password_validation(self):
resp = self.client.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": "john@example.com",
"password1": "johndoe",
"password2": "johndoe",
},
)
if django.VERSION >= (4, 1):
self.assertFormError(resp.context["form"], None, [])
self.assertFormError(
resp.context["form"],
"password1",
["This password is too short. It must contain at least 9 characters."],
)
else:
self.assertFormError(resp, "form", None, [])
self.assertFormError(
resp,
"form",
"password1",
["This password is too short. It must contain at least 9 characters."],
)
def test_prevent_enumeration_with_mandatory_verification(settings, user_factory):
settings.ACCOUNT_PREVENT_ENUMERATION = True
settings.ACCOUNT_AUTHENTICATION_METHOD = app_settings.AuthenticationMethod.EMAIL
settings.ACCOUNT_EMAIL_VERIFICATION = app_settings.EmailVerificationMethod.MANDATORY
user = user_factory(username="john", email="john@example.org", password="doe")
c = Client()
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": user.email,
"password1": "johndoe",
"password2": "johndoe",
},
)
assert resp.status_code == 302
assert resp["location"] == reverse("account_email_verification_sent")
assertTemplateUsed(resp, "account/email/account_already_exists_message.txt")
assert EmailAddress.objects.filter(email="john@example.org").count() == 1
def test_prevent_enumeration_off(settings, user_factory):
settings.ACCOUNT_PREVENT_ENUMERATION = False
settings.ACCOUNT_AUTHENTICATION_METHOD = app_settings.AuthenticationMethod.EMAIL
settings.ACCOUNT_EMAIL_VERIFICATION = app_settings.EmailVerificationMethod.MANDATORY
user = user_factory(username="john", email="john@example.org", password="doe")
c = Client()
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": user.email,
"password1": "johndoe",
"password2": "johndoe",
},
)
assert resp.status_code == 200
assert resp.context["form"].errors == {
"email": ["A user is already registered with this email address."]
}
def test_prevent_enumeration_strictly(settings, user_factory):
settings.ACCOUNT_PREVENT_ENUMERATION = "strict"
settings.ACCOUNT_AUTHENTICATION_METHOD = app_settings.AuthenticationMethod.EMAIL
settings.ACCOUNT_EMAIL_VERIFICATION = app_settings.EmailVerificationMethod.NONE
user = user_factory(username="john", email="john@example.org", password="doe")
c = Client()
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": user.email,
"password1": "johndoe",
"password2": "johndoe",
},
)
assert resp.status_code == 302
assert resp["location"] == settings.LOGIN_REDIRECT_URL
assert EmailAddress.objects.filter(email="john@example.org").count() == 2
def test_prevent_enumeration_on(settings, user_factory):
settings.ACCOUNT_PREVENT_ENUMERATION = True
settings.ACCOUNT_AUTHENTICATION_METHOD = app_settings.AuthenticationMethod.EMAIL
settings.ACCOUNT_EMAIL_VERIFICATION = app_settings.EmailVerificationMethod.NONE
user = user_factory(username="john", email="john@example.org", password="doe")
c = Client()
resp = c.post(
reverse("account_signup"),
{
"username": "johndoe",
"email": user.email,
"password1": "johndoe",
"password2": "johndoe",
},
)
assert resp.status_code == 200
assert resp.context["form"].errors == {
"email": ["A user is already registered with this email address."]
}
@pytest.mark.django_db
def test_get_initial_with_valid_email():
"""Test that the email field is populated with a valid email."""
request = RequestFactory().get("/signup/?email=test@example.com")
from allauth.account.views import signup
SessionMiddleware(lambda request: None).process_request(request)
request.user = AnonymousUser()
with context.request_context(request):
view = signup(request)
assert view.context_data["view"].get_initial()["email"] == "test@example.com"

View File

@@ -0,0 +1,128 @@
from __future__ import absolute_import
import uuid
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.messages.api import get_messages
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail, validators
from django.core.exceptions import ValidationError
from django.template import Context, Template
from django.test.client import RequestFactory
from django.test.utils import override_settings
import allauth.app_settings
from allauth.account.adapter import get_adapter
from allauth.account.models import EmailAddress
from allauth.account.utils import (
filter_users_by_username,
url_str_to_user_pk,
user_pk_to_url_str,
user_username,
)
from allauth.core import context
from allauth.tests import TestCase, patch
from .test_models import UUIDUser
test_username_validators = [
validators.RegexValidator(regex=r"^[a-c]+$", message="not abc", flags=0)
]
class UtilsTests(TestCase):
def setUp(self):
self.user_id = uuid.uuid4().hex
def test_url_str_to_pk_identifies_UUID_as_stringlike(self):
with patch("allauth.account.utils.get_user_model") as mocked_gum:
mocked_gum.return_value = UUIDUser
self.assertEqual(url_str_to_user_pk(self.user_id), uuid.UUID(self.user_id))
def test_pk_to_url_string_identifies_UUID_as_stringlike(self):
with patch("allauth.account.utils.get_user_model") as mocked_gum:
mocked_gum.return_value = UUIDUser
user = UUIDUser(is_active=True, email="john@example.com", username="john")
self.assertEqual(user_pk_to_url_str(user), user.pk.hex)
@override_settings(ACCOUNT_PRESERVE_USERNAME_CASING=False)
def test_username_lower_cased(self):
user = get_user_model()()
user_username(user, "CamelCase")
self.assertEqual(user_username(user), "camelcase")
# TODO: Actually test something
filter_users_by_username("CamelCase", "FooBar")
@override_settings(ACCOUNT_PRESERVE_USERNAME_CASING=True)
def test_username_case_preserved(self):
user = get_user_model()()
user_username(user, "CamelCase")
self.assertEqual(user_username(user), "CamelCase")
# TODO: Actually test something
filter_users_by_username("camelcase", "foobar")
def test_user_display(self):
user = get_user_model()(username="john<br/>doe")
expected_name = "john&lt;br/&gt;doe"
templates = [
"{% load account %}{% user_display user %}",
"{% load account %}{% user_display user as x %}{{ x }}",
]
for template in templates:
t = Template(template)
content = t.render(Context({"user": user}))
self.assertEqual(content, expected_name)
def test_message_escaping(self):
request = RequestFactory().get("/")
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
user = get_user_model()()
user_username(user, "'<8")
context = {"user": user}
get_adapter().add_message(
request, messages.SUCCESS, "account/messages/logged_in.txt", context
)
msgs = get_messages(request)
actual_message = msgs._queued_messages[0].message
assert user.username in actual_message, actual_message
def test_email_escaping(self):
site_name = "testserver"
if allauth.app_settings.SITES_ENABLED:
from django.contrib.sites.models import Site
site = Site.objects.get_current()
site.name = site_name = '<enc&"test>'
site.save()
u = get_user_model().objects.create(username="test", email="user@example.com")
request = RequestFactory().get("/")
EmailAddress.objects.add_email(request, u, u.email, confirm=True)
self.assertTrue(mail.outbox[0].subject[1:].startswith(site_name))
@override_settings(
ACCOUNT_USERNAME_VALIDATORS="allauth.account.tests.test_utils.test_username_validators"
)
def test_username_validator(self):
get_adapter().clean_username("abc")
self.assertRaises(ValidationError, lambda: get_adapter().clean_username("def"))
@override_settings(ALLOWED_HOSTS=["allowed_host", "testserver"])
def test_is_safe_url_no_wildcard(self):
with context.request_context(RequestFactory().get("/")):
self.assertTrue(get_adapter().is_safe_url("http://allowed_host/"))
self.assertFalse(get_adapter().is_safe_url("http://other_host/"))
@override_settings(ALLOWED_HOSTS=["*"])
def test_is_safe_url_wildcard(self):
with context.request_context(RequestFactory().get("/")):
self.assertTrue(get_adapter().is_safe_url("http://foobar.com/"))
self.assertTrue(get_adapter().is_safe_url("http://other_host/"))
@override_settings(ALLOWED_HOSTS=["allowed_host", "testserver"])
def test_is_safe_url_relative_path(self):
with context.request_context(RequestFactory().get("/")):
self.assertTrue(get_adapter().is_safe_url("/foo/bar"))