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
160 changes: 160 additions & 0 deletions source/site_ops/concepts/user_retirement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
.. _User Retirement Concept:

What is User Retirement?
########################

.. tags:: site operator, concept

**User retirement** is the Open edX toolset for handling requests to delete a
user account and to remove or obfuscate that user's Personally Identifiable
Information (PII) from the platform. A learner can request that their account
be deleted, and site operators can then run a process that erases or scrambles
that learner's PII across the services that make up a particular Open edX site
(the LMS, forums, credentials, and others).

.. important::

User retirement is **not** a compliance guarantee. The Open edX software
makes no claim of satisfying any law or regulation. It is a configurable
toolset that site operators can use to help meet the obligations apply to
them specifically. Custom code, configuration, plugins, packages, or XBlocks
on your site may store PII that this feature does not know about and will
not clean up. Deciding what data must be removed, and confirming that it has
been, remains the operator's responsibility.

Why it works the way it does
****************************

PII about a single user can live in many places: the LMS database, the
discussion forums, the credentials service, and potentially external systems
such as third-party marketing or email tools. There is no single "delete user"
button that can reach all of those places at once. Instead, user retirement is
built as a *configurable pipeline* of small steps, each of which retires some
of the user's data in one system. Operators can add, remove, or reorder
steps to match the services their site actually uses.

The main pieces
***************

User retirement is made up of a few moving parts that work together.

The account deletion UI
=======================

The learner-facing entry point is a **Delete My Account** section on the
learner's Account page. When a learner confirms deletion (by entering their
password), three things happen immediately:

* All of the learner's browser sessions are logged out and the account is
disabled, so the learner can no longer sign in.
* A row is created in the LMS database (the ``UserRetirementStatus`` record)
that marks the account as awaiting retirement.
* A confirmation email is sent to the learner.

At this point the account is **deactivated but not yet retired** — no PII has
been erased. Whether this UI is shown is controlled by the
``ENABLE_ACCOUNT_DELETION`` feature setting in the LMS.

The cool-off period
===================

After a deletion request, the account sits in a waiting state (called
``PENDING``) for a configurable number of *cool-off days*. During this window a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How are cool-off days configured?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure that belongs in this doc, but it is covered in the new how-to I just added. I can link it here if you think that makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it'd be good to reference the how-to here, yes

learner who changed their mind can contact the site's support or operator and
have the request cancelled, restoring the account. Only once an account has
been in the ``PENDING`` state longer than the cool-off period is it considered
ready for actual retirement. This gives operators a safety margin against
accidental or regretted deletions.

The retirement process
======================

The retirement process is the batch job that actually carries out deletions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume the retirement process is not enabled by default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The process itself isn't, but the UI to delete your account is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense, maybe this could be clarified with something like

Suggested change
The retirement process is the batch job that actually carries out deletions.
The retirement process is a manually-configured batch job that actually carries out deletions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's somewhat confusing in that if you install the Tutor plugin and are on k8s it will actually create a cron for it and start running the pipeline. In all other cases you need to do something manual to kick the job. For this doc I was just trying to outline the concept of the process so didn't want to have to get into the weeds of various ways things might be configured.

It is driven by a set of Python scripts (the *driver* scripts) that typically
run on a schedule, for example once a day:

#. One script asks the LMS for the list of learners whose cool-off period has
elapsed and who are therefore ready to retire.
#. A second script then walks each of those learners, one at a time, through
the retirement pipeline.

Retirement is modeled as a **state machine**. The LMS is the authoritative
record of where every user is in the process, tracking each user's current
state (such as ``PENDING``, ``RETIRING_ENROLLMENTS``, ``ENROLLMENTS_COMPLETE``,
and so on) and moving them forward one step at a time. The list of possible
states is stored in the LMS database rather than hard-coded, so operators can
tailor the pipeline to their needs.

Every user's journey ends in one of a few **terminal states**:

``COMPLETE``
The user was retired successfully and their record can be cleaned up.
``ABORTED``
The request was cancelled during the cool-off period.
``ERRORED``
A step failed and needs an operator to investigate and resolve it.

Each step has a "working" state such as ``RETIRING_ENROLLMENTS`` and a "complete"
state such as ``ENROLLMENTS_COMPLETE``. The first indicates that the step is in
progress, while the second indicates that it has completed successfully and the
user can move on to the next step.

Because the pipeline is linear and re-runnable, a run that fails partway through
can be safely restarted by changing the user's state manually in the LMS Django
admin's ``UserRetirementStatus`` section. This area also shows any errors
from the previous run, and the last state that a user was in before a failure.

If a users's ``UserRetirementStatus`` row is set to ``PENDING`` or any
``_COMPLETE`` state (ex: ``ENROLLMENTS_COMPLETE``), the pipeline will pick
up from the *following* step on the next pipeine run.

.. important::

Setting a user's state to any **working** state (ex: ``RETIRING_ENROLLMENTS``)
will cause the user to be seen as "in progress" by the pipeline and ignored.
They will be stuck in a partially retired state until manually moved. It
is important to occasionally check that no users have been left in a working
state for too long.

The retirement APIs
===================

Each step in the pipeline calls an API in one of the platform's services. Most
of these live in the LMS and do the real work of forgetting a user such as
unenrolling them from courses, scrubbing their forum posts, and scrambling their
username and email so that neither can be reused. The retirement pipeline
configuration maps each step to the specific service and API method it should
call, which is how operators extend the process to cover additional services or
custom data.

Deploying user retirement with Tutor
************************************

Setting up user retirement involves several steps: creating a dedicated
service user with API credentials, loading the pipeline states into the LMS,
and scheduling the scripts to run.

For sites deployed with Tutor, the community-maintained
`tutor-contrib-retirement <https://github.com/cleura/tutor-contrib-retirement>`_
plugin automates most of this. It registers the OAuth2 credentials for the
retirement service worker, populates the pipeline states in the LMS, and
provides a command (and, on Kubernetes, a scheduled job) to run the retirement
process on a regular cadence with a configurable cool-off period. While not an
official part of an Open edX release, it is well maintained.

.. seealso::

`Enabling the User Retirement Feature <https://docs.openedx.org/projects/edx-platform/en/latest/references/docs/scripts/user_retirement/docs/index.html>`_
In-depth reference documentation on configuring the services, setting up
the driver scripts, and extending the retirement pipeline.

`tutor-contrib-retirement <https://github.com/cleura/tutor-contrib-retirement>`_
Community-maintained Tutor plugin for deploying user retirement.

**Maintenance chart**

+--------------+-------------------------------+----------------+--------------------------------+
| Review Date | Working Group Reviewer | Release |Test situation |
+--------------+-------------------------------+----------------+--------------------------------+
| 2026-07-28 | sarina | Verawood | Pass |
+--------------+-------------------------------+----------------+--------------------------------+
208 changes: 208 additions & 0 deletions source/site_ops/how-tos/set_up_user_retirement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
.. _Set Up User Retirement:

Set Up User Retirement
######################

.. tags:: site operator, how-to

This how-to walks through enabling the :ref:`user retirement <User Retirement Concept>`
feature so that your site can act on learner requests to delete their accounts.
For background on how retirement works, see :ref:`User Retirement Concept`.

Setting retirement up by hand involves creating a dedicated service user with
API credentials, loading your instance's specific pipeline states into the LMS,
and scheduling the driver scripts to run. The steps below use the
community-maintained
`tutor-contrib-retirement <https://github.com/cleura/tutor-contrib-retirement>`_
plugin, which automates that setup for Tutor deployments. If you do not use
Tutor, see :ref:`Configure retirement without Tutor` below.

.. note::

This plugin is not an official part of an Open edX release and focuses on
LMS retirement. If your site stores PII in other services (ex: analytics, A/B
testing, 3rd party email, etc.) and you want to retire that data using this
process, you will need to extend the pipeline yourself. See the reference docs
linked under :ref:`See Also <User Retirement See Also>`.

Prerequisites
*************

* A working Tutor deployment that you can restart.
* Shell access to the host running Tutor.
* Permission to make LMS Django admin changes, which you will use to operate
the feature after setup.

Enable the plugin with Tutor
****************************

#. **Choose a compatible plugin version.** The plugin is versioned to match
Open edX and Tutor releases. Consult the `version compatibility matrix
<https://github.com/cleura/tutor-contrib-retirement#version-compatibility-matrix>`_
and pick the release (or branch) that matches the version you are deploying.

#. **Install the plugin**, substituting the version you selected above:

.. code-block:: shell

pip install git+https://github.com/cleura/tutor-contrib-retirement@v5.4.2

#. **Enable the plugin:**

.. code-block:: shell

tutor plugins enable retirement

#. **Build the retirement Docker image**, which packages the driver scripts:

.. code-block:: shell

tutor images build retirement

#. **Initialize your deployment** so the plugin can register the retirement
service worker as an OAuth2 client in the LMS and populate the retirement
pipeline stages:

.. code-block:: shell

# for a local deployment
tutor local do init

# for a Kubernetes deployment
tutor k8s do init

Configure the cool-off period
*****************************

The *cool-off period* is the number of days an account waits in the
``PENDING`` state before it becomes eligible for retirement, giving learners a
window to rescind their request. It defaults to 30 days.

To change it (and any other setting), use ``tutor config save``:

.. code-block:: shell

tutor config save --set RETIREMENT_COOL_OFF_DAYS=14

Rescinding a retirement during cool-off
***************************************

Generally to rescind a retirmement request (only during the cool-off period), the user
would contact the institution's support. In order for support to rescind a retirement
on behalf of a user they can use the management command:

.. code-block:: shell

# for a local deployment
tutor local run lms ./manage.py lms cancel_user_retirement_request <original_email_address>

The command takes the following steps:

#. Restores the user's email address back to the original.
#. Resets the password to a random 25-char value — so the learner must go through **password reset** to log back in.
#. Deletes the `UserRetirementStatus` row (the "permanent" retirement request record is cleaned up via a Django signal).

Retirement settings
*******************

The available settings and their defaults are:

- ``ENABLE_ACCOUNT_DELETION`` (default ``True``)
Whether or not the learner facing "Delete My Account" flow is enabled.

- ``RETIREMENT_COOL_OFF_DAYS`` (default ``30``)
Days an account must sit in ``PENDING`` before it is retired.

- ``RETIREMENT_EDX_OAUTH2_CLIENT_ID`` (default ``retirement_service_worker``)
OAuth2 client ID registered for the retirement service worker.

- ``RETIREMENT_K8S_CRONJOB_SCHEDULE`` (default ``"0 0 * * *"``)
Cron schedule for the Kubernetes retirement job (default: daily at midnight).

- ``RETIREMENT_K8S_CRONJOB_HISTORYLIMIT_SUCCESS`` (default ``3``)
Number of successful job runs to retain in history.

- ``RETIREMENT_K8S_CRONJOB_HISTORYLIMIT_FAILURE`` (default ``1``)
Number of failed job runs to retain in history.

After saving configuration (``tutor config save``), re-initialize the deployment (ex:
``tutor local do init``) so the new values take effect.

Run the retirement pipeline
***************************

Running the pipeline retires every learner whose cool-off period has elapsed,
walking each one through the pipeline stages.

For a **local** deployment, run it on demand:

.. code-block:: shell

tutor local retire-users

To run it regularly, invoke that command from a cron job on the host, for
example once a day.

For a **Kubernetes** deployment, the plugin installs a ``CronJob`` that runs
the pipeline automatically on the schedule set by
``RETIREMENT_K8S_CRONJOB_SCHEDULE`` — no manual step is required.

Operate the feature
*******************

Day-to-day operation happens in the LMS Django admin, under the
``UserRetirementStatus`` section.

* **Confirm the deletion UI is available.** The learner-facing **Delete My
Account** section is controlled by the ``ENABLE_ACCOUNT_DELETION`` feature in
the LMS settings. It is enabled by default.

* **Cancel a request during cool-off.** If a learner asks to keep their
account, cancel their request while it is still in the ``PENDING`` state. This
moves the account to the ``ABORTED`` terminal state and restores it.

* **Recover from an error.** If a user lands in the ``ERRORED`` state, the
``UserRetirementStatus`` record shows the last state reached and the error.
After resolving the underlying problem, set the user's state back to
``PENDING`` or to the ``_COMPLETE`` state of the last step that succeeded; the
next run picks up from the following step.

.. important::

Do not leave a user in a *working* state such as ``RETIRING_ENROLLMENTS``.
The pipeline treats those users as "in progress" and skips them, so they stay
partially retired until moved manually. Check periodically that no accounts
are stuck in a working state.

.. _Configure retirement without Tutor:

Configure retirement without Tutor
**********************************

If you do not use Tutor, you must perform the same setup manually: create the
retirement service user and OAuth2 credentials, load the pipeline states into
the LMS, configure the driver scripts, and schedule them to run. The Open edX
reference documentation covers each of these steps in detail; see the links
below.

.. _User Retirement See Also:

.. seealso::

:ref:`User Retirement Concept`
Conceptual overview of how user retirement works.

`Enabling the User Retirement Feature <https://docs.openedx.org/projects/edx-platform/en/latest/references/docs/scripts/user_retirement/docs/index.html>`_
In-depth reference documentation on configuring the services, setting up
the driver scripts, and extending the retirement pipeline.

`tutor-contrib-retirement <https://github.com/cleura/tutor-contrib-retirement>`_
Community-maintained Tutor plugin for deploying user retirement.

**Maintenance chart**

+--------------+-------------------------------+----------------+--------------------------------+
| Review Date | Working Group Reviewer | Release |Test situation |
+--------------+-------------------------------+----------------+--------------------------------+
| 2026-07-28 | sarina | Verawood | Pass |
+--------------+-------------------------------+----------------+--------------------------------+