Skip to content

Commit eb5b4df

Browse files
fix: redacting user retirement data in lms
1 parent a7cdaa6 commit eb5b4df

3 files changed

Lines changed: 44 additions & 15 deletions

File tree

openedx/core/djangoapps/user_api/accounts/views.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,15 +1025,19 @@ def cleanup(self, request):
10251025
```
10261026
{
10271027
'usernames': ['user1', 'user2', ...],
1028-
'redacted_value': 'Value to store in PII'
1028+
'redacted_username': 'Value to store in username field',
1029+
'redacted_email': 'Value to store in email field',
1030+
'redacted_name': 'Value to store in name field'
10291031
}
10301032
```
10311033
1032-
Redacts a batch of retirement requests by redacting PII fields and username.
1034+
Redacts a batch of retirement requests by redacting PII fields.
10331035
"""
10341036
try:
10351037
usernames = request.data["usernames"]
1036-
redacted_value = request.data.get("redacted_value", "redacted")
1038+
redacted_username = request.data.get("redacted_username", "redacted")
1039+
redacted_email = request.data.get("redacted_email", "redacted")
1040+
redacted_name = request.data.get("redacted_name", "redacted")
10371041

10381042
if not isinstance(usernames, list):
10391043
raise TypeError("Usernames should be an array.")
@@ -1050,9 +1054,9 @@ def cleanup(self, request):
10501054
# Redact PII fields instead of deleting records to prevent ETL tools
10511055
# from creating soft deletes with visible PII in downstream data warehouses
10521056
for retirement in retirements:
1053-
retirement.original_username = redacted_value
1054-
retirement.original_email = redacted_value
1055-
retirement.original_name = redacted_value
1057+
retirement.original_username = redacted_username
1058+
retirement.original_email = redacted_email
1059+
retirement.original_name = redacted_name
10561060
retirement.save()
10571061

10581062
return Response(status=status.HTTP_204_NO_CONTENT)

scripts/user_retirement/retirement_archive_and_cleanup.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,16 @@ def _archive_retirements_or_exit(config, learners, dry_run=False):
196196
FAIL_EXCEPTION(ERR_ARCHIVING, 'Unexpected error occurred archiving retirements!', exc)
197197

198198

199-
def _cleanup_retirements_or_exit(config, learners):
199+
def _cleanup_retirements_or_exit(config, learners, redacted_username='redacted', redacted_email='redacted', redacted_name='redacted'):
200200
"""
201-
Bulk deletes the retirements for this run
201+
Bulk redacts the retirements for this run
202202
"""
203203
LOG('Cleaning up retirements for {} learners'.format(len(learners)))
204204
try:
205205
usernames = [l['original_username'] for l in learners]
206-
config['LMS'].bulk_cleanup_retirements(usernames)
206+
config['LMS'].bulk_cleanup_retirements(usernames, redacted_username, redacted_email, redacted_name)
207207
except Exception as exc: # pylint: disable=broad-except
208-
FAIL_EXCEPTION(ERR_DELETING, 'Unexpected error occurred deleting retirements!', exc)
208+
FAIL_EXCEPTION(ERR_DELETING, 'Unexpected error occurred redacting retirements!', exc)
209209

210210

211211
def _get_utc_now():
@@ -222,7 +222,7 @@ def _get_utc_now():
222222
)
223223
@click.option(
224224
'--cool_off_days',
225-
help='Number of days a retirement should exist before being archived and deleted.',
225+
help='Number of days a retirement should exist before being archived and redacted.',
226226
type=int,
227227
default=37 # 7 days before retirement, 30 after
228228
)
@@ -259,7 +259,25 @@ def _get_utc_now():
259259
help='Number of user retirements to process',
260260
type=int
261261
)
262-
def archive_and_cleanup(config_file, cool_off_days, dry_run, start_date, end_date, batch_size):
262+
@click.option(
263+
'--redacted_username',
264+
help='Value to redact username field with',
265+
type=str,
266+
default='redacted'
267+
)
268+
@click.option(
269+
'--redacted_email',
270+
help='Value to redact email field with',
271+
type=str,
272+
default='redacted'
273+
)
274+
@click.option(
275+
'--redacted_name',
276+
help='Value to redact name field with',
277+
type=str,
278+
default='redacted'
279+
)
280+
def archive_and_cleanup(config_file, cool_off_days, dry_run, start_date, end_date, batch_size, redacted_username, redacted_email, redacted_name):
263281
"""
264282
Cleans up UserRetirementStatus rows in LMS by:
265283
1- Getting all rows currently in COMPLETE that were created --cool_off_days ago or more,
@@ -314,7 +332,7 @@ def archive_and_cleanup(config_file, cool_off_days, dry_run, start_date, end_dat
314332
if dry_run:
315333
LOG('This is a dry-run. Exiting before any retirements are cleaned up')
316334
else:
317-
_cleanup_retirements_or_exit(config, batch)
335+
_cleanup_retirements_or_exit(config, batch, redacted_username, redacted_email, redacted_name)
318336
LOG('Archive and cleanup complete for batch #{}'.format(str(index + 1)))
319337
time.sleep(DELAY)
320338
else:

scripts/user_retirement/utils/edx_api.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,18 @@ def retirement_retire_proctoring_backend_data(self, learner):
352352
return self._request("POST", api_url)
353353

354354
@_retry_lms_api()
355-
def bulk_cleanup_retirements(self, usernames):
355+
def bulk_cleanup_retirements(self, usernames, redacted_username=None, redacted_email=None, redacted_name=None):
356356
"""
357-
Deletes the retirements for all given usernames
357+
Redacts the retirements for all given usernames.
358+
Optionally pass caller-defined redacted values for each PII field.
358359
"""
359360
data = {"usernames": usernames}
361+
if redacted_username is not None:
362+
data['redacted_username'] = redacted_username
363+
if redacted_email is not None:
364+
data['redacted_email'] = redacted_email
365+
if redacted_name is not None:
366+
data['redacted_name'] = redacted_name
360367
api_url = self.get_api_url("api/user/v1/accounts/retirement_cleanup")
361368
return self._request("POST", api_url, json=data)
362369

0 commit comments

Comments
 (0)