Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 12 additions & 31 deletions common/djangoapps/student/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,31 +210,13 @@ def user_by_anonymous_id(uid):
def is_username_retired(username):
"""
Checks to see if the given username has been previously retired
"""
locally_hashed_usernames = user_util.get_all_retired_usernames(
username,
settings.RETIRED_USER_SALTS,
settings.RETIRED_USERNAME_FMT
)

# TODO: Revert to this after username capitalization issues detailed in
# PLAT-2276, PLAT-2277, PLAT-2278 are sorted out:
# return User.objects.filter(username__in=list(locally_hashed_usernames)).exists()

# Avoid circular import issues
from openedx.core.djangoapps.user_api.models import UserRetirementStatus

# Sandbox clean builds attempt to create users during migrations, before the database
# is stable so UserRetirementStatus may not exist yet. This workaround can also go
# when we are done with the username updates.
try:
return User.objects.filter(username__in=list(locally_hashed_usernames)).exists() or \
UserRetirementStatus.objects.filter(original_username=username).exists()
except ProgrammingError as exc:
# Check the error message to make sure it's what we expect
if "user_api_userretirementstatus" in str(exc):
return User.objects.filter(username__in=list(locally_hashed_usernames)).exists()
raise
Modified to allow retired usernames to be reused for new registrations.
This enables users to re-register with the same username after account retirement.
The new registration will be a completely separate account with no previous data.
"""
# Allow retired usernames to be reused
return False


def username_exists_or_retired(username):
Expand All @@ -247,14 +229,13 @@ def username_exists_or_retired(username):
def is_email_retired(email):
"""
Checks to see if the given email has been previously retired
"""
locally_hashed_emails = user_util.get_all_retired_emails(
email,
settings.RETIRED_USER_SALTS,
settings.RETIRED_EMAIL_FMT
)

return User.objects.filter(email__in=list(locally_hashed_emails)).exists()
Modified to allow retired emails to be reused for new registrations.
This enables users to re-register with the same email after account retirement.
The new registration will be a completely separate account with no previous data.
"""
# Allow retired emails to be reused
return False


def email_exists_or_retired(email):
Expand Down
11 changes: 9 additions & 2 deletions openedx/core/djangoapps/user_api/accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,22 @@ def create_retirement_request_and_deactivate_account(user):
"""
Adds user to retirement queue, unlinks social auth accounts, changes user passwords
and delete tokens and activation keys

Modified to include user ID in retired credentials to allow reuse of original credentials.
"""
# Add user to retirement queue.
UserRetirementStatus.create_retirement(user)

# Unlink LMS social auth accounts
UserSocialAuth.objects.filter(user_id=user.id).delete()

# Change LMS password & email
user.email = get_retired_email_by_email(user.email)
# Change LMS password, username & email
# Include user ID to ensure uniqueness when retired credentials are reused
retired_username = f"retired__user_{user.id}_{user.username}"
retired_email = f"retired__user_{user.id}_{user.email.split('@')[0]}@retired.invalid"

user.username = retired_username
user.email = retired_email
user.set_unusable_password()
user.save()

Expand Down
7 changes: 5 additions & 2 deletions openedx/core/djangoapps/user_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ def create_retirement(cls, user):
"""
Creates a UserRetirementStatus for the given user, in the correct initial state. Will
fail if the user already has a UserRetirementStatus row or if states are not yet populated.

Modified to include user ID in retired credentials to allow reuse of original credentials.
"""
try:
pending = RetirementState.objects.all().order_by('state_execution_order')[0]
Expand All @@ -349,8 +351,9 @@ def create_retirement(cls, user):
if cls.objects.filter(user=user).exists():
raise RetirementStateError(f'User {user} already has a retirement status row!')

retired_username = get_retired_username_by_username(user.username)
retired_email = get_retired_email_by_email(user.email)
# Include user ID to ensure uniqueness when retired credentials are reused
retired_username = f"retired__user_{user.id}_{user.username}"
retired_email = f"retired__user_{user.id}_{user.email.split('@')[0]}@retired.invalid"

UserRetirementRequest.create_retirement_request(user)

Expand Down
Loading