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