diff --git a/source/site_ops/concepts/user_retirement.rst b/source/site_ops/concepts/user_retirement.rst new file mode 100644 index 000000000..d4aec608e --- /dev/null +++ b/source/site_ops/concepts/user_retirement.rst @@ -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. +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 `_ +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 `_ + In-depth reference documentation on configuring the services, setting up + the driver scripts, and extending the retirement pipeline. + + `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 | ++--------------+-------------------------------+----------------+--------------------------------+ diff --git a/source/site_ops/how-tos/set_up_user_retirement.rst b/source/site_ops/how-tos/set_up_user_retirement.rst new file mode 100644 index 000000000..9e196099d --- /dev/null +++ b/source/site_ops/how-tos/set_up_user_retirement.rst @@ -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 ` +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 `_ +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 `. + +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 + `_ + 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 + +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 `_ + In-depth reference documentation on configuring the services, setting up + the driver scripts, and extending the retirement pipeline. + + `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 | ++--------------+-------------------------------+----------------+--------------------------------+