Skip to content

Commit 3dca82d

Browse files
committed
feat: introduce IGNORE_LOGGED_IN_USER_ON_MISMATCH toggle and updated changelog
1 parent c3dd58f commit 3dca82d

4 files changed

Lines changed: 125 additions & 247 deletions

File tree

CHANGELOG.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ Unreleased
1313
----------
1414

1515

16+
[4.6.1] - 2025-07-25
17+
--------------------
18+
19+
Added
20+
~~~~~
21+
22+
* Added IGNORE_LOGGED_IN_USER_ON_MISMATCH toggle to handle username mismatches in authentication pipeline.
23+
* Enhanced get_user_if_exists function with monitoring capabilities for debugging authentication conflicts.
24+
25+
Fixed
26+
~~~~~
27+
28+
* Fixed authentication issues where username mismatches between logged-in users and social auth details caused "user already exists" errors.
29+
* Improved user account conflict resolution in devstack and stage environments.
30+
1631
[4.6.0] - 2025-06-18
1732
--------------------
1833

auth_backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
These package is designed to be used primarily with Open edX Django projects, but should be compatible with non-edX
44
projects as well.
55
"""
6-
__version__ = '4.6.0' # pragma: no cover
6+
__version__ = '4.6.1' # pragma: no cover

auth_backends/pipeline.py

Lines changed: 33 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -22,150 +22,68 @@
2222
# .. toggle_target_removal_date: 2025-08-18
2323
SKIP_UPDATE_EMAIL_ON_USERNAME_MISMATCH = SettingToggle("SKIP_UPDATE_EMAIL_ON_USERNAME_MISMATCH", default=False)
2424

25-
# .. toggle_name: DEBUG_GET_USER_IF_EXISTS
25+
# .. toggle_name: IGNORE_LOGGED_IN_USER_ON_MISMATCH
2626
# .. toggle_implementation: SettingToggle
27-
# .. toggle_default: False
28-
# .. toggle_description: Enables detailed debugging and monitoring for the get_user_if_exists pipeline function.
29-
# When enabled (True), additional logging and custom attributes will be set to help debug
30-
# user account conflicts and authentication issues.
27+
# .. toggle_default: True
28+
# .. toggle_description: Controls behavior when there's a username mismatch between the logged-in user
29+
# and social auth details. When enabled (True), ignores the logged-in user and proceeds with
30+
# user lookup from social auth details. When disabled (False), proceeds with the logged-in user
31+
# despite the mismatch. This toggle is for temporary rollout only to ensure we don't create bugs.
3132
# .. toggle_use_cases: temporary
32-
# .. toggle_creation_date: 2025-07-23
33-
# .. toggle_target_removal_date: 2025-09-23
34-
DEBUG_GET_USER_IF_EXISTS = SettingToggle("DEBUG_GET_USER_IF_EXISTS", default=False)
33+
# .. toggle_creation_date: 2025-07-25
34+
# .. toggle_target_removal_date: 2025-09-25
35+
IGNORE_LOGGED_IN_USER_ON_MISMATCH = SettingToggle("IGNORE_LOGGED_IN_USER_ON_MISMATCH", default=True)
3536

3637

3738
# pylint: disable=unused-argument
3839
# The function parameters must be named exactly as they are below.
3940
# Do not change them to appease Pylint.
4041
def get_user_if_exists(strategy, details, user=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg
41-
"""Return a User with the given username iff the User exists.
42-
43-
Enhanced with debugging capabilities to track user account conflicts and authentication issues.
4442
"""
45-
details_username = details.get('username')
46-
47-
# Set custom attributes for debugging
48-
# .. custom_attribute_name: get_user_if_exists.details_username
49-
# .. custom_attribute_description: Records the username provided in the social details
50-
# to help debug authentication and user lookup issues.
51-
set_custom_attribute('get_user_if_exists.details_username', details_username)
52-
53-
# .. custom_attribute_name: get_user_if_exists.user_provided
54-
# .. custom_attribute_description: Indicates whether a user object was already provided
55-
# to the pipeline function, which affects the lookup logic.
56-
set_custom_attribute('get_user_if_exists.user_provided', user is not None)
57-
58-
# .. custom_attribute_name: get_user_if_exists.debug_enabled
59-
# .. custom_attribute_description: Tracks whether the DEBUG_GET_USER_IF_EXISTS
60-
# toggle is enabled during this pipeline execution.
61-
set_custom_attribute('get_user_if_exists.debug_enabled', DEBUG_GET_USER_IF_EXISTS.is_enabled())
62-
43+
Return a User with the given username iff the User exists.
44+
"""
6345
if user:
64-
# User is already provided - this typically happens when user exists from previous pipeline steps
65-
existing_username = getattr(user, 'username', None)
66-
67-
# .. custom_attribute_name: get_user_if_exists.existing_user_username
68-
# .. custom_attribute_description: Records the username of the existing user object
69-
# when a user is already provided to the pipeline.
70-
set_custom_attribute('get_user_if_exists.existing_user_username', existing_username)
71-
72-
# Check for username mismatch between provided user and details
73-
username_mismatch = details_username != existing_username
46+
# Check for username mismatch and toggle behavior
47+
details_username = details.get('username')
48+
user_username = getattr(user, 'username', None)
49+
username_mismatch = details_username != user_username
7450

7551
# .. custom_attribute_name: get_user_if_exists.username_mismatch
7652
# .. custom_attribute_description: Tracks whether there's a mismatch between
77-
# the username in details and the existing user's username.
53+
# the username in the social details and the user's actual username.
54+
# True if usernames don't match, False if they match.
7855
set_custom_attribute('get_user_if_exists.username_mismatch', username_mismatch)
7956

80-
if DEBUG_GET_USER_IF_EXISTS.is_enabled() or username_mismatch:
81-
logger.info(
82-
"get_user_if_exists: User already provided. Username mismatch: %s. "
83-
"Details username: %s, Existing user username: %s",
84-
username_mismatch,
85-
details_username,
86-
existing_username
87-
)
57+
# .. custom_attribute_name: get_user_if_exists.ignore_toggle_enabled
58+
# .. custom_attribute_description: Tracks whether the IGNORE_LOGGED_IN_USER_ON_MISMATCH
59+
# toggle is enabled during this pipeline execution.
60+
set_custom_attribute('get_user_if_exists.ignore_toggle_enabled', IGNORE_LOGGED_IN_USER_ON_MISMATCH.is_enabled())
8861

89-
if username_mismatch:
90-
logger.warning(
91-
"Username mismatch in get_user_if_exists. Details username: %s, "
92-
"Existing user username: %s. This may indicate an authentication issue.",
93-
details_username,
94-
existing_username
62+
if username_mismatch and IGNORE_LOGGED_IN_USER_ON_MISMATCH.is_enabled():
63+
logger.info(
64+
"Username mismatch detected. Details: %s, User: %s. Ignoring logged-in user.",
65+
details_username, user_username
9566
)
96-
97-
return {'is_new': False}
98-
99-
# No user provided, attempt to find user by username from details
100-
if not details_username:
101-
logger.warning("get_user_if_exists: No username provided in details")
102-
# .. custom_attribute_name: get_user_if_exists.no_username_in_details
103-
# .. custom_attribute_description: Indicates that no username was provided in the details,
104-
# which may indicate an issue with the authentication provider.
105-
set_custom_attribute('get_user_if_exists.no_username_in_details', True)
106-
return {}
67+
else:
68+
return {'is_new': False}
10769

10870
try:
109-
found_user = User.objects.get(username=details_username)
110-
111-
# .. custom_attribute_name: get_user_if_exists.user_found
112-
# .. custom_attribute_description: Indicates that a user was successfully found
113-
# by username lookup in the database.
114-
set_custom_attribute('get_user_if_exists.user_found', True)
115-
116-
# .. custom_attribute_name: get_user_if_exists.found_user_id
117-
# .. custom_attribute_description: Records the ID of the user found by username lookup
118-
# to help track user account conflicts.
119-
set_custom_attribute('get_user_if_exists.found_user_id', found_user.id)
120-
121-
if DEBUG_GET_USER_IF_EXISTS.is_enabled():
122-
logger.info(
123-
"get_user_if_exists: Found existing user with username '%s' (ID: %s)",
124-
details_username,
125-
found_user.id
126-
)
71+
username = details.get('username')
12772

128-
# Return the user if it exists
12973
return {
13074
'is_new': False,
131-
'user': found_user
75+
'user': User.objects.get(username=username)
13276
}
13377
except User.DoesNotExist:
134-
# .. custom_attribute_name: get_user_if_exists.user_found
135-
# .. custom_attribute_description: Indicates that no user was found
136-
# by username lookup in the database.
137-
set_custom_attribute('get_user_if_exists.user_found', False)
138-
139-
if DEBUG_GET_USER_IF_EXISTS.is_enabled():
140-
logger.info(
141-
"get_user_if_exists: No user found with username '%s'",
142-
details_username
143-
)
78+
pass
14479

145-
except Exception as e:
146-
# Handle any unexpected errors during user lookup
147-
logger.error(
148-
"get_user_if_exists: Unexpected error during user lookup for username '%s': %s",
149-
details_username,
150-
str(e)
151-
)
152-
153-
# .. custom_attribute_name: get_user_if_exists.lookup_error
154-
# .. custom_attribute_description: Indicates that an unexpected error occurred
155-
# during user lookup, which may indicate database or system issues.
156-
set_custom_attribute('get_user_if_exists.lookup_error', True)
157-
158-
# .. custom_attribute_name: get_user_if_exists.error_message
159-
# .. custom_attribute_description: Records the error message when an unexpected
160-
# error occurs during user lookup.
161-
set_custom_attribute('get_user_if_exists.error_message', str(e))
162-
163-
# Nothing to return since we don't have a user
16480
return {}
16581

16682

16783
def update_email(strategy, details, user=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg
168-
"""Update the user's email address using data from provider."""
84+
"""
85+
Update the user's email address using data from provider.
86+
"""
16987

17088
if user:
17189
# Get usernames for comparison, using defensive coding

0 commit comments

Comments
 (0)