Skip to content

fix: retirement PII leaks by redacting pending secondary email/name data#227

Open
ktyagiapphelix2u wants to merge 2 commits intorelease-ulmofrom
ktyagi/secondary
Open

fix: retirement PII leaks by redacting pending secondary email/name data#227
ktyagiapphelix2u wants to merge 2 commits intorelease-ulmofrom
ktyagi/secondary

Conversation

@ktyagiapphelix2u
Copy link
Copy Markdown

Summary

This PR closes retirement-related PII leak gaps for secondary email and related pending records in LMS.

It ensures all retirement entry points consistently redact sensitive fields before deletion, so downstream soft-delete mirrors do not retain original plaintext values.

What changed

Added retirement cleanup for pending secondary email records in all retirement flows.
Added retirement cleanup for account recovery secondary email in all retirement flows.
Updated admin retirement flow to also set user email to retired hashed value.
Hardened pending email retirement handling to redact pending email before delete.
Hardened pending name retirement handling to clear pending name/rationale before delete.
Kept existing retirement behavior for profile/certificate/social data and validated coverage.
Added/updated tests for:
Secondary email cleanup on deactivation flow
Secondary email cleanup on management command flow
Secondary/account recovery cleanup on admin retirement flow

Why

Before this change, some retirement paths could leave or soft-delete plaintext PII (especially pending secondary email, pending email change, and pending name change records).
This PR enforces redact-then-delete behavior consistently across API and command paths.

Ticket & Reference

https://2u-internal.atlassian.net/browse/BOMS-499
https://2u-internal.atlassian.net/wiki/spaces/AT/pages/3503652877/Secondary+Email+BOMS+-+499

Copilot AI review requested due to automatic review settings April 10, 2026 07:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Closes retirement-related PII leak gaps by ensuring pending secondary email change records and account recovery secondary emails are redacted before deletion across retirement entry points, preventing downstream soft-delete mirrors from retaining plaintext values.

Changes:

  • Added redact-then-delete retirement helpers for pending secondary email changes and improved redaction for account recovery secondary emails.
  • Updated retirement flows (API, management command, deactivation utils) to retire pending secondary email/account recovery consistently; hardened pending email/name retirement redaction.
  • Added/updated tests to validate secondary email cleanup across flows.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
openedx/core/djangoapps/user_api/management/tests/test_retire_user.py Adds management-command test coverage for pending secondary email cleanup.
openedx/core/djangoapps/user_api/management/commands/retire_user.py Ensures management command retires pending secondary email records.
openedx/core/djangoapps/user_api/accounts/views.py Hardens retirement endpoints: redact pending name/email before delete; retires secondary/account recovery; sets user.email to retired value.
openedx/core/djangoapps/user_api/accounts/utils.py Ensures deactivation flow retires pending secondary email records.
openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py Adds tests to validate pending secondary + account recovery cleanup during retirement.
common/djangoapps/student/views/management.py Redacts pending secondary email before deletion during activation flow.
common/djangoapps/student/tests/test_models.py Adds model-level tests for retiring pending secondary email changes.
common/djangoapps/student/models/user.py Introduces PendingSecondaryEmailChange.retire_pending_secondary_email and updates AccountRecovery retirement to redact before delete.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

RevisionPluginRevision.retire_user(retirement.user)
ArticleRevision.retire_user(retirement.user)
# Redact PendingNameChange before deletion to prevent plaintext sync to Snowflake
PendingNameChange.objects.filter(user=retirement.user).update(new_name="", rationale="")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ktyagiapphelix2u, Do we not want to redact the field instead of making this empty string?

activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True)

@classmethod
def retire_pending_secondary_email(cls, user_id):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ktyagiapphelix2u , you may want to rename this function to redact_pending_secondary_email instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants