From 1a5f8ecc5a0721c95406cdc9408918401830997b Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Fri, 2 Jan 2026 12:39:15 +0100 Subject: [PATCH] feat: add flag to allow permanent user deletions Use a feature flag to enable permanent deleting a user and their associated objects without going through the retirement pipeline where some of the PII is anonymized but not ignored when the user decides to register a second time with the same email or username. --- .../edxapp_wrapper/backends/users_q_v1.py | 49 ++++++++++++++++++- eox_core/settings/common.py | 1 + 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/eox_core/edxapp_wrapper/backends/users_q_v1.py b/eox_core/edxapp_wrapper/backends/users_q_v1.py index 5f27b58f..cfd1a0f8 100644 --- a/eox_core/edxapp_wrapper/backends/users_q_v1.py +++ b/eox_core/edxapp_wrapper/backends/users_q_v1.py @@ -265,9 +265,41 @@ def get_edxapp_user(**kwargs): return user -def delete_edxapp_user(*args, **kwargs): +def permanently_delete_user(*args, **kwargs): + """Hard deletes a user from the platform. + + This function permanently removes the user from the database. It is + irreversible and should be used with caution. All associated objects should + be handled via cascading deletes or other mechanisms as needed. The + only object manually deleted here are the OAuth tokens associated with the user. """ - Deletes a user from the platform. + user = kwargs.get("user") + + with transaction.atomic(): + # Delete OAuth tokens associated with the user before deleting the user. + # These are not set to cascade delete automatically. + retire_dot_oauth2_models(user) + UserSocialAuth.objects.filter(user_id=user.id).delete() + + user.delete() + + return ( + f"The user {user.username} <{user.email}> has been permanently deleted", + status.HTTP_200_OK, + ) + + +def retire_user_with_cascade_delete(*args, **kwargs): + """Deletes a user from the platform by retiring them. + + This function renames the user's email and username to indicate that + the account has been retired, adds the user to the retirement queue, unlinks + social auth accounts, sets an unusable password, removes activation keys, + deletes OAuth tokens, and deletes the user's signup source for the specified + site. + + If the user tries to register again with the same email or username, the + system will recognize that the account has been retired. """ msg = None @@ -321,6 +353,19 @@ def delete_edxapp_user(*args, **kwargs): raise NotFound(f"{user_response} does not have a signup source on the site {site}") +def delete_edxapp_user(*args, **kwargs): + """Deletes a user from the platform by either retiring or permanently deleting them. + + The method used depends on the EOX_CORE_ALLOW_PERMANENT_USER_DELETION setting. + + If EOX_CORE_ALLOW_PERMANENT_USER_DELETION is set to True, the user will be + permanently deleted from the database. This action is irreversible. + """ + if getattr(settings, "EOX_CORE_ALLOW_PERMANENT_USER_DELETION", False): + return permanently_delete_user(*args, **kwargs) + return retire_user_with_cascade_delete(*args, **kwargs) + + def get_course_team_user(*args, **kwargs): """ Get _course_team_user function. diff --git a/eox_core/settings/common.py b/eox_core/settings/common.py index 3a783c5c..ef3389be 100644 --- a/eox_core/settings/common.py +++ b/eox_core/settings/common.py @@ -51,6 +51,7 @@ def plugin_settings(settings): settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND = 'eox_core.edxapp_wrapper.backends.third_party_auth_l_v1' settings.EOX_CORE_LANG_PREF_BACKEND = 'eox_core.edxapp_wrapper.backends.lang_pref_middleware_p_v1' settings.EOX_CORE_JWT_SIGNED_OAUTH_APP_PUBLIC_KEY = '' + settings.EOX_CORE_ALLOW_PERMANENT_USER_DELETION = False if settings.EOX_CORE_USER_ENABLE_MULTI_TENANCY: settings.EOX_CORE_USER_ORIGIN_SITE_SOURCES = [