Skip to content

Commit 68fbf10

Browse files
committed
fix: bug in third party authentication email and name update
1 parent b92924f commit 68fbf10

3 files changed

Lines changed: 89 additions & 8 deletions

File tree

auth_backends/pipeline.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
33
For more info visit http://python-social-auth.readthedocs.org/en/latest/pipeline.html.
44
"""
5+
import logging
56
from django.contrib.auth import get_user_model
67

8+
from edx_django_utils.monitoring import set_custom_attribute
79

10+
logger = logging.getLogger(__name__)
811
User = get_user_model()
912

1013

@@ -34,8 +37,38 @@ def get_user_if_exists(strategy, details, user=None, *args, **kwargs): # pylint
3437
def update_email(strategy, details, user=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg
3538
"""Update the user's email address using data from provider."""
3639
if user:
37-
email = details.get('email')
40+
# Get usernames for comparison, using defensive coding
41+
details_username = details.get('username')
42+
user_username = getattr(user, 'username', None)
43+
# Check if usernames match
44+
username_match = details_username == user_username
45+
46+
# .. custom_attribute_name: update_email.username_mismatch
47+
# .. custom_attribute_description: Tracks whether there's a mismatch between
48+
# the username in the authentication details and the user's actual username.
49+
# True if usernames don't match, False if they match.
50+
set_custom_attribute('update_email.username_mismatch', not username_match)
51+
52+
if not username_match:
53+
# Log warning and set additional custom attributes for mismatches
54+
logger.warning(
55+
"Username mismatch during email update. User username: %s, Details username: %s",
56+
user_username,
57+
details_username
58+
)
59+
# .. custom_attribute_name: update_email.details_username
60+
# .. custom_attribute_description: Records the username provided in the
61+
# authentication details when a mismatch occurs with the user's username.
62+
set_custom_attribute('update_email.details_username', str(details_username))
3863

64+
# .. custom_attribute_name: update_email.user_username
65+
# .. custom_attribute_description: Records the actual username of the user
66+
# when a mismatch occurs with the authentication details username.
67+
set_custom_attribute('update_email.user_username', str(user_username))
68+
return # Exit without updating email
69+
70+
# Proceed with email update only if usernames match
71+
email = details.get('email')
3972
if email and user.email != email:
4073
user.email = email
4174
strategy.storage.user.changed(user)

auth_backends/tests/test_pipeline.py

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
""" Tests for pipelines. """
22

3+
from unittest.mock import patch, MagicMock
34
from django.contrib.auth import get_user_model
45
from django.test import TestCase
5-
from social_django.utils import load_strategy
66

77
from auth_backends.pipeline import get_user_if_exists, update_email
88

@@ -40,20 +40,67 @@ class UpdateEmailPipelineTests(TestCase):
4040

4141
def setUp(self):
4242
super().setUp()
43-
self.user = User.objects.create()
43+
self.user = User.objects.create(username='test_user')
44+
self.strategy = MagicMock()
4445

45-
def test_update_email(self):
46-
""" Verify that user email is updated upon changing email. """
46+
# Make strategy.storage.user.changed actually save the user
47+
def save_user(user):
48+
user.save()
49+
50+
self.strategy.storage.user.changed.side_effect = save_user
51+
52+
@patch('auth_backends.pipeline.set_custom_attribute')
53+
def test_update_email(self, mock_set_attribute):
54+
""" Verify that user email is updated upon changing email when usernames match. """
4755
updated_email = 'updated@example.com'
4856
self.assertNotEqual(self.user.email, updated_email)
4957

50-
update_email(load_strategy(), {'email': updated_email}, user=self.user)
58+
# Provide matching username in details
59+
update_email(self.strategy, {'email': updated_email, 'username': 'test_user'}, user=self.user)
60+
61+
# Verify email was updated
5162
self.user = User.objects.get(username=self.user.username)
5263
self.assertEqual(self.user.email, updated_email)
64+
self.strategy.storage.user.changed.assert_called_once_with(self.user)
65+
66+
# Verify custom attribute was set correctly
67+
mock_set_attribute.assert_called_with('update_email.username_mismatch', False)
5368

54-
def test_update_email_with_none(self):
69+
@patch('auth_backends.pipeline.set_custom_attribute')
70+
def test_update_email_with_none(self, mock_set_attribute):
5571
""" Verify that user email is not updated if email value is None. """
5672
old_email = self.user.email
57-
update_email(load_strategy(), {'email': None}, user=self.user)
73+
74+
# Provide matching username in details
75+
update_email(self.strategy, {'email': None, 'username': 'test_user'}, user=self.user)
76+
77+
# Verify email was not updated
78+
self.user = User.objects.get(username=self.user.username)
79+
self.assertEqual(self.user.email, old_email)
80+
self.strategy.storage.user.changed.assert_not_called()
81+
82+
# Verify custom attribute was still set
83+
mock_set_attribute.assert_called_with('update_email.username_mismatch', False)
84+
85+
@patch('auth_backends.pipeline.logger')
86+
@patch('auth_backends.pipeline.set_custom_attribute')
87+
def test_username_mismatch_no_update(self, mock_set_attribute, mock_logger):
88+
""" Verify that email is not updated when usernames don't match. """
89+
old_email = self.user.email
90+
updated_email = 'updated@example.com'
91+
92+
# Provide mismatched username in details
93+
update_email(self.strategy, {'email': updated_email, 'username': 'different_user'}, user=self.user)
94+
95+
# Verify email was not updated
5896
self.user = User.objects.get(username=self.user.username)
5997
self.assertEqual(self.user.email, old_email)
98+
self.strategy.storage.user.changed.assert_not_called()
99+
100+
# Verify logger was called with warning
101+
mock_logger.warning.assert_called_once()
102+
103+
# Verify all custom attributes were set correctly
104+
mock_set_attribute.assert_any_call('update_email.username_mismatch', True)
105+
mock_set_attribute.assert_any_call('update_email.details_username', 'different_user')
106+
mock_set_attribute.assert_any_call('update_email.user_username', 'test_user')

requirements/base.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ pyjwt[crypto]>=2.1.0 # depends on newer jwt.decode and jwt.encode function
66
six
77
social-auth-app-django
88
social-auth-core # Common auth interfaces
9+
edx-django-utils

0 commit comments

Comments
 (0)