Files
Iliyan Angelov c67067a2a4 Mail
2025-09-14 23:24:25 +03:00

527 lines
21 KiB
Python

import django
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
import allauth.app_settings
from allauth.account import app_settings as account_settings
from allauth.account.models import EmailAddress
from allauth.account.utils import user_email, user_username
from allauth.core import context
from allauth.socialaccount import providers
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialAccount, SocialApp, SocialLogin
from allauth.socialaccount.views import signup
from allauth.tests import TestCase
from allauth.utils import get_user_model
class SignupTests(TestCase):
def setUp(self):
super().setUp()
for provider in providers.registry.get_class_list():
if provider.id == "openid_connect":
continue
app = SocialApp.objects.create(
provider=provider.id,
name=provider.id,
client_id="app123id",
key="123",
secret="dummy",
)
if allauth.app_settings.SITES_ENABLED:
from django.contrib.sites.models import Site
site = Site.objects.get_current()
app.sites.add(site)
@override_settings(
SOCIALACCOUNT_AUTO_SIGNUP=True,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.NONE, # noqa
)
def test_email_address_created(self):
factory = RequestFactory()
request = factory.get("/accounts/login/callback/")
request.user = AnonymousUser()
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
User = get_user_model()
user = User()
setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, "test")
setattr(user, account_settings.USER_MODEL_EMAIL_FIELD, "test@example.com")
account = SocialAccount(provider="openid", uid="123")
sociallogin = SocialLogin(user=user, account=account)
with context.request_context(request):
complete_social_login(request, sociallogin)
user = User.objects.get(**{account_settings.USER_MODEL_USERNAME_FIELD: "test"})
self.assertTrue(
SocialAccount.objects.filter(user=user, uid=account.uid).exists()
)
self.assertTrue(
EmailAddress.objects.filter(user=user, email=user_email(user)).exists()
)
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=True,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=True,
)
def test_email_address_clash_username_required(self):
"""Test clash on both username and email"""
request, resp = self._email_address_clash("test", "test@example.com")
self.assertEqual(resp["location"], reverse("socialaccount_signup"))
# POST different username/email to social signup form
request.method = "POST"
request.POST = {"username": "other", "email": "other@example.com"}
with context.request_context(request):
resp = signup(request)
self.assertEqual(resp["location"], "/accounts/profile/")
user = get_user_model().objects.get(
**{account_settings.USER_MODEL_EMAIL_FIELD: "other@example.com"}
)
self.assertEqual(user_username(user), "other")
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=False,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=True,
)
def test_email_address_clash_username_not_required(self):
"""Test clash while username is not required"""
request, resp = self._email_address_clash("test", "test@example.com")
self.assertEqual(resp["location"], reverse("socialaccount_signup"))
# POST email to social signup form (username not present)
request.method = "POST"
request.POST = {"email": "other@example.com"}
with context.request_context(request):
resp = signup(request)
self.assertEqual(resp["location"], "/accounts/profile/")
user = get_user_model().objects.get(
**{account_settings.USER_MODEL_EMAIL_FIELD: "other@example.com"}
)
self.assertNotEqual(user_username(user), "test")
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=False,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=True,
)
def test_email_address_clash_username_auto_signup(self):
# Clash on username, but auto signup still works
request, resp = self._email_address_clash("test", "other@example.com")
self.assertEqual(resp["location"], "/accounts/profile/")
user = get_user_model().objects.get(
**{account_settings.USER_MODEL_EMAIL_FIELD: "other@example.com"}
)
self.assertNotEqual(user_username(user), "test")
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_USERNAME_BLACKLIST=["username", "username1", "username2"],
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=True,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=True,
)
def test_populate_username_in_blacklist(self):
factory = RequestFactory()
request = factory.get("/accounts/twitter/login/callback/")
request.user = AnonymousUser()
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
User = get_user_model()
user = User()
setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, "username")
setattr(
user,
account_settings.USER_MODEL_EMAIL_FIELD,
"username@example.com",
)
account = SocialAccount(provider="twitter", uid="123")
sociallogin = SocialLogin(user=user, account=account)
with context.request_context(request):
complete_social_login(request, sociallogin)
self.assertNotIn(request.user.username, account_settings.USERNAME_BLACKLIST)
def _email_address_clash(self, username, email):
User = get_user_model()
# Some existig user
exi_user = User()
user_username(exi_user, "test")
exi_user_email = "test@example.com"
user_email(exi_user, exi_user_email)
exi_user.save()
EmailAddress.objects.create(
user=exi_user, email=exi_user_email, verified=True, primary=True
)
# A social user being signed up...
account = SocialAccount(provider="twitter", uid="123")
user = User()
user_username(user, username)
user_email(user, email)
sociallogin = SocialLogin(user=user, account=account)
# Signing up, should pop up the social signup form
factory = RequestFactory()
request = factory.get("/accounts/twitter/login/callback/")
request.user = AnonymousUser()
SessionMiddleware(lambda request: None).process_request(request)
MessageMiddleware(lambda request: None).process_request(request)
with context.request_context(request):
resp = complete_social_login(request, sociallogin)
return request, resp
def test_disconnect(self):
User = get_user_model()
# Some existig user
user = User()
user_username(user, "test")
user_email(user, "test@example.com")
user.set_password("test")
user.save()
account = SocialAccount.objects.create(uid="123", provider="twitter", user=user)
self.client.login(username=user.username, password=user.username)
resp = self.client.get(reverse("socialaccount_connections"))
self.assertTemplateUsed(resp, "socialaccount/connections.html")
resp = self.client.post(
reverse("socialaccount_connections"), {"account": account.pk}
)
self.assertFalse(SocialAccount.objects.filter(pk=account.pk).exists())
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_EMAIL_VERIFICATION="mandatory",
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=False,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=False,
)
def test_verified_email_change_at_signup(self):
"""
Test scenario for when the user changes email at social signup. Current
behavior is that both the unverified and verified email are added, and
that the user is allowed to pass because he did provide a verified one.
"""
session = self.client.session
User = get_user_model()
sociallogin = SocialLogin(
user=User(email="verified@example.com"),
account=SocialAccount(provider="google"),
email_addresses=[
EmailAddress(email="verified@example.com", verified=True, primary=True)
],
)
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
resp = self.client.get(reverse("socialaccount_signup"))
form = resp.context["form"]
self.assertEqual(form["email"].value(), "verified@example.com")
resp = self.client.post(
reverse("socialaccount_signup"),
data={"email": "unverified@example.org"},
)
self.assertRedirects(resp, "/accounts/profile/", fetch_redirect_response=False)
user = User.objects.all()[0]
self.assertEqual(user_email(user), "verified@example.com")
self.assertTrue(
EmailAddress.objects.filter(
user=user,
email="verified@example.com",
verified=True,
primary=True,
).exists()
)
self.assertTrue(
EmailAddress.objects.filter(
user=user,
email="unverified@example.org",
verified=False,
primary=False,
).exists()
)
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_EMAIL_VERIFICATION="mandatory",
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=False,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=False,
)
def test_unverified_email_change_at_signup(self):
"""
Test scenario for when the user changes email at social signup, while
his provider did not provide a verified email. In that case, email
verification will kick in. Here, both email addresses are added as
well.
"""
session = self.client.session
User = get_user_model()
sociallogin = SocialLogin(
user=User(email="unverified@example.com"),
account=SocialAccount(provider="google"),
email_addresses=[
EmailAddress(
email="unverified@example.com",
verified=False,
primary=True,
)
],
)
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
resp = self.client.get(reverse("socialaccount_signup"))
form = resp.context["form"]
self.assertEqual(form["email"].value(), "unverified@example.com")
resp = self.client.post(
reverse("socialaccount_signup"),
data={"email": "unverified@example.org"},
)
self.assertRedirects(resp, reverse("account_email_verification_sent"))
user = User.objects.all()[0]
self.assertEqual(user_email(user), "unverified@example.org")
self.assertTrue(
EmailAddress.objects.filter(
user=user,
email="unverified@example.com",
verified=False,
primary=False,
).exists()
)
self.assertTrue(
EmailAddress.objects.filter(
user=user,
email="unverified@example.org",
verified=False,
primary=True,
).exists()
)
@override_settings(
ACCOUNT_PREVENT_ENUMERATION=False,
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_EMAIL_VERIFICATION="mandatory",
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=False,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=False,
)
def test_unique_email_validation_signup(self):
session = self.client.session
User = get_user_model()
email = "me@example.com"
user = User.objects.create(email=email)
EmailAddress.objects.create(email=email, user=user, verified=True)
sociallogin = SocialLogin(
user=User(email="me@example.com"),
account=SocialAccount(provider="google"),
email_addresses=[
EmailAddress(email="me@example.com", verified=True, primary=True)
],
)
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
resp = self.client.get(reverse("socialaccount_signup"))
form = resp.context["form"]
self.assertEqual(form["email"].value(), email)
resp = self.client.post(reverse("socialaccount_signup"), data={"email": email})
if django.VERSION >= (4, 1):
self.assertFormError(
resp.context["form"],
"email",
"An account already exists with this email address."
" Please sign in to that account first, then connect"
" your Google account.",
)
else:
self.assertFormError(
resp,
"form",
"email",
"An account already exists with this email address."
" Please sign in to that account first, then connect"
" your Google account.",
)
@override_settings(
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_EMAIL_VERIFICATION="mandatory",
ACCOUNT_UNIQUE_EMAIL=True,
ACCOUNT_USERNAME_REQUIRED=True,
ACCOUNT_AUTHENTICATION_METHOD="email",
SOCIALACCOUNT_AUTO_SIGNUP=False,
)
def test_social_account_taken_at_signup(self):
"""
Test scenario for when the user signs up with a social account
and uses email address in that social account. But upon seeing the
verification screen, they realize that email address is somehow
unusable for them, and so backs up and enters a different email
address (and is forced to choose a new username) while providing
the same social account token which is owned by their first attempt.
"""
session = self.client.session
User = get_user_model()
sociallogin = SocialLogin(
user=User(email="me1@example.com"),
account=SocialAccount(provider="facebook"),
)
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
resp = self.client.get(reverse("socialaccount_signup"))
form = resp.context["form"]
self.assertEqual(form["email"].value(), "me1@example.com")
resp = self.client.post(
reverse("socialaccount_signup"),
data={"username": "me1", "email": "me1@example.com"},
)
self.assertEqual(resp.status_code, 302)
self.assertEqual(User.objects.count(), 1)
self.assertEqual(SocialAccount.objects.count(), 1)
resp = self.client.get(reverse("socialaccount_signup"))
self.assertRedirects(resp, reverse("account_login"))
def test_email_address_required_missing_from_sociallogin(
db, settings, sociallogin_factory, client, rf
):
"""Tests that when the email address is missing from the sociallogin email
verification kicks in.
"""
settings.ACCOUNT_EMAIL_REQUIRED = True
settings.ACCOUNT_UNIQUE_EMAIL = True
settings.ACCOUNT_USERNAME_REQUIRED = False
settings.ACCOUNT_AUTHENTICATION_METHOD = "email"
settings.ACCOUNT_EMAIL_VERIFICATION = "mandatory"
settings.SOCIALACCOUNT_AUTO_SIGNUP = True
sociallogin = sociallogin_factory(with_email=False)
request = rf.get("/")
request.session = {}
request.user = AnonymousUser()
resp = complete_social_login(request, sociallogin)
assert resp["location"] == reverse("socialaccount_signup")
session = client.session
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
resp = client.post(reverse("socialaccount_signup"), {"email": "other@example.org"})
assert resp["location"] == reverse("account_email_verification_sent")
def test_email_address_conflict_at_social_signup_form(
db, settings, user_factory, sociallogin_factory, client, rf, mailoutbox
):
"""Tests that when an already existing email is given at the social signup
form, enumeration preventation kicks in.
"""
settings.ACCOUNT_EMAIL_REQUIRED = True
settings.ACCOUNT_UNIQUE_EMAIL = True
settings.ACCOUNT_USERNAME_REQUIRED = False
settings.ACCOUNT_AUTHENTICATION_METHOD = "email"
settings.ACCOUNT_EMAIL_VERIFICATION = "mandatory"
settings.SOCIALACCOUNT_AUTO_SIGNUP = True
user = user_factory()
sociallogin = sociallogin_factory(with_email=False)
request = rf.get("/")
request.session = {}
request.user = AnonymousUser()
resp = complete_social_login(request, sociallogin)
# Auto signup does not kick in as the `sociallogin` does not have an email.
assert resp["location"] == reverse("socialaccount_signup")
session = client.session
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
# Here, we input the already existing email.
resp = client.post(reverse("socialaccount_signup"), {"email": user.email})
assert mailoutbox[0].subject == "[example.com] Account Already Exists"
assert resp["location"] == reverse("account_email_verification_sent")
def test_email_address_conflict_during_auto_signup(
db, settings, user_factory, sociallogin_factory, client, rf, mailoutbox
):
"""Tests that when an already existing email is received from the provider,
enumeration preventation kicks in.
"""
settings.ACCOUNT_EMAIL_REQUIRED = True
settings.ACCOUNT_UNIQUE_EMAIL = True
settings.ACCOUNT_USERNAME_REQUIRED = False
settings.ACCOUNT_AUTHENTICATION_METHOD = "email"
settings.ACCOUNT_EMAIL_VERIFICATION = "mandatory"
settings.SOCIALACCOUNT_AUTO_SIGNUP = True
user = user_factory()
sociallogin = sociallogin_factory(email=user.email, with_email=True)
request = rf.get("/")
request.session = {}
request.user = AnonymousUser()
resp = complete_social_login(request, sociallogin)
assert resp["location"] == reverse("account_email_verification_sent")
assert mailoutbox[0].subject == "[example.com] Account Already Exists"
def test_email_address_conflict_removes_conflicting_email(
db, settings, user_factory, sociallogin_factory, client, rf, mailoutbox
):
"""Tests that when an already existing email is given at the social signup
form, enumeration preventation kicks in.
"""
settings.ACCOUNT_EMAIL_REQUIRED = True
settings.ACCOUNT_UNIQUE_EMAIL = True
settings.ACCOUNT_USERNAME_REQUIRED = False
settings.ACCOUNT_AUTHENTICATION_METHOD = "email"
settings.ACCOUNT_EMAIL_VERIFICATION = "optional"
settings.SOCIALACCOUNT_AUTO_SIGNUP = True
settings.SOCIALACCOUNT_EMAIL_AUTHENTICATION = False
user = user_factory(email_verified=False)
sociallogin = sociallogin_factory(email=user.email, email_verified=False)
request = rf.get("/")
request.session = {}
request.user = AnonymousUser()
resp = complete_social_login(request, sociallogin)
# Auto signup does not kick in as the `sociallogin` has a conflicting email.
assert resp["location"] == reverse("socialaccount_signup")
session = client.session
session["socialaccount_sociallogin"] = sociallogin.serialize()
session.save()
# Here, we input the already existing email.
resp = client.post(reverse("socialaccount_signup"), {"email": "other@email.org"})
assert mailoutbox[0].subject == "[example.com] Please Confirm Your Email Address"
assert resp["location"] == settings.LOGIN_REDIRECT_URL
assert EmailAddress.objects.filter(email=user.email).count() == 1