Skip to content

Commit 10c80e2

Browse files
committed
fix: avoid associating with existing user when creating fails
This behavior was introduced in 9f86059 to workaround concurrency issues, but the only safe way to deal with this is to restart the pipeline to make sure that all possible policies apply. This is currently not possible, so let's fail with AuthAlreadyAssociated and let user restart the authentication pipeline manually.
1 parent cd16cfa commit 10c80e2

File tree

2 files changed

+15
-14
lines changed

2 files changed

+15
-14
lines changed

social_django/storage.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.core.exceptions import FieldDoesNotExist
1010
from django.db import router, transaction
1111
from django.db.utils import IntegrityError
12+
from social_core.exceptions import AuthAlreadyAssociated
1213
from social_core.storage import (
1314
AssociationMixin,
1415
BaseStorage,
@@ -81,20 +82,15 @@ def create_user(cls, *args, **kwargs):
8182
cls.user_model()._meta.get_field("username") # noqa: SLF001
8283
except FieldDoesNotExist:
8384
kwargs.pop("username")
85+
86+
# If the create fails below due to an IntegrityError, ensure that the transaction
87+
# stays undamaged by wrapping the create in an atomic.
88+
using = router.db_for_write(cls.user_model())
8489
try:
85-
# If the create fails below due to an IntegrityError, ensure that the transaction
86-
# stays undamaged by wrapping the create in an atomic.
87-
using = router.db_for_write(cls.user_model())
8890
with transaction.atomic(using=using):
8991
return manager.create_user(*args, **kwargs)
9092
except IntegrityError as exc:
91-
# If email comes in as None it won't get found in the get
92-
if kwargs.get("email", True) is None:
93-
kwargs["email"] = ""
94-
try:
95-
return manager.get(*args, **kwargs)
96-
except cls.user_model().DoesNotExist:
97-
raise exc from None
93+
raise AuthAlreadyAssociated(None) from exc
9894

9995
@classmethod
10096
def filter_users(cls, *args, **kwargs) -> QuerySet:

tests/test_models.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.core.management import call_command
66
from django.db import IntegrityError
77
from django.test import TestCase, override_settings
8+
from social_core.exceptions import AuthAlreadyAssociated
89

910
from social_django.models import (
1011
AbstractUserSocialAuth,
@@ -101,18 +102,22 @@ def test_get_username(self):
101102
self.assertEqual(UserSocialAuth.get_username(self.user), self.user.username)
102103

103104
def test_create_user(self):
104-
# Catch integrity error and find existing user
105-
UserSocialAuth.create_user(username=self.user.username)
105+
UserSocialAuth.create_user(username="testuser")
106106

107107
def test_create_user_reraise(self):
108-
with self.assertRaises(IntegrityError):
108+
with self.assertRaises(AuthAlreadyAssociated):
109109
UserSocialAuth.create_user(username=self.user.username, email=None)
110110

111111
@mock.patch("social_django.models.UserSocialAuth.username_field", return_value="email")
112-
@mock.patch("django.contrib.auth.models.UserManager.create_user", side_effect=IntegrityError)
112+
@mock.patch("django.contrib.auth.models.UserManager.create_user", return_value="<User>")
113113
def test_create_user_custom_username(self, *args):
114114
UserSocialAuth.create_user(username=self.user.email)
115115

116+
@mock.patch("django.contrib.auth.models.UserManager.create_user", side_effect=IntegrityError)
117+
def test_create_user_existing(self, *args):
118+
with self.assertRaises(AuthAlreadyAssociated):
119+
UserSocialAuth.create_user(username=self.user.email)
120+
116121
def test_get_user(self):
117122
self.assertEqual(UserSocialAuth.get_user(pk=self.user.pk), self.user)
118123
self.assertIsNone(UserSocialAuth.get_user(pk=123))

0 commit comments

Comments
 (0)