-
Notifications
You must be signed in to change notification settings - Fork 90
docs: Add retirement concept doc #1488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||||||
| 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume the retirement process is not enabled by default?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, maybe this could be clarified with something like
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | | ||||||
| +--------------+-------------------------------+----------------+--------------------------------+ | ||||||
| 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 | | ||
| +--------------+-------------------------------+----------------+--------------------------------+ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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