Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 12 additions & 0 deletions src/ol_social_auth/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@ Make sure to properly configure the plugin following the links in the above "Con
* Install the plugin in the lms following the installation steps above.
* Verify that you are not logged in on edx-platform.
* Create a new user in your MIT application and verify that a corresponding user is successfully created on the edX platform.

Expired Token Cleanup
---------------------
This plugin includes a scheduled Celery task (``clear_expired_tokens``) that automatically removes expired OAuth2 access tokens, refresh tokens, and grant tokens from the database.

**Behavior:**

* Runs every **Monday at 9:00 AM** (server time) via Celery Beat.
* Uses django-oauth-toolkit's ``clear_expired()`` to delete tokens that have exceeded the configured expiration threshold.
* Sets ``REFRESH_TOKEN_EXPIRE_SECONDS`` to **30 days** (overriding the edx-platform default of 90 days). Tokens revoked or expired longer than 30 days ago will be cleaned up.

**Note:** If running this plugin for the first time on a database with a large backlog of expired tokens (millions of rows), consider running the ``edx_clear_expired_tokens`` management command manually first to reduce the initial volume before relying on the scheduled task.
23 changes: 23 additions & 0 deletions src/ol_social_auth/ol_social_auth/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""ol_social_auth Django application initialization."""

from django.apps import AppConfig
from edx_django_utils.plugins import PluginSettings
from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType


class OLSocialAuthConfig(AppConfig):
name = "ol_social_auth"
verbose_name = "OL Social Auth"

plugin_app = {
PluginSettings.CONFIG: {
ProjectType.LMS: {
SettingsType.COMMON: {
PluginSettings.RELATIVE_PATH: "settings.common",
},
SettingsType.PRODUCTION: {
PluginSettings.RELATIVE_PATH: "settings.production",
},
},
},
}
Empty file.
16 changes: 16 additions & 0 deletions src/ol_social_auth/ol_social_auth/settings/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Common settings for the ol-social-auth plugin."""

from celery.schedules import crontab


def plugin_settings(settings):
"""Add clear_expired_tokens to the Celery beat schedule."""
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.

The docstrings are for the while plugin_settings, so they need to be generic.

settings.OAUTH2_PROVIDER["REFRESH_TOKEN_EXPIRE_SECONDS"] = (
30 * 24 * 60 * 60 # 30 days
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'd suggest checking with @blarghmatey this time range as well, since we're using service tokens before merging the PR. Since DevOps creates service tokens, I'm not sure what expiry time they set. I want to make sure they will not be deleted automatically.

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.

The OAuth service accounts that we rely on are using client credentials grants so we're not worried about token expiration. Thanks for checking.

)
Comment thread
Anas12091101 marked this conversation as resolved.
if not hasattr(settings, "CELERY_BEAT_SCHEDULE"):
settings.CELERY_BEAT_SCHEDULE = {}
settings.CELERY_BEAT_SCHEDULE["clear_expired_tokens"] = {
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.

Let's distinguish it.

Suggested change
settings.CELERY_BEAT_SCHEDULE["clear_expired_tokens"] = {
settings.CELERY_BEAT_SCHEDULE["ol_clear_expired_tokens"] = {

"task": "ol_social_auth.tasks.clear_expired_tokens",
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.

Let's do it here as well

Suggested change
"task": "ol_social_auth.tasks.clear_expired_tokens",
"task": "ol_social_auth.tasks.ol_clear_expired_tokens",

"schedule": crontab(hour=9, minute=0, day_of_week="monday"),
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.

Why not midnight?

Suggested change
"schedule": crontab(hour=9, minute=0, day_of_week="monday"),
"schedule": crontab(hour=0, minute=0, day_of_week="monday"),

Copy link
Copy Markdown
Contributor Author

@Anas12091101 Anas12091101 Apr 8, 2026

Choose a reason for hiding this comment

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

I’m not entirely sure, but this task is scheduled for 9 AM in our other applications as well; for example, MITxOnline and MITxPRO

@pdpinch, what are your thoughts on this?

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.

If it's a task that we care about succeeding, I prefer to do it during business hours, so we have a better chance of noticing a problem and fixing it.

For this task, like this, really, any time would be fine.

}
5 changes: 5 additions & 0 deletions src/ol_social_auth/ol_social_auth/settings/production.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Production settings for the ol-social-auth plugin."""


def plugin_settings(settings):
"""Production overrides for ol-social-auth plugin."""
16 changes: 16 additions & 0 deletions src/ol_social_auth/ol_social_auth/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Celery tasks for ol-social-auth plugin."""

import logging

from celery import shared_task
from oauth2_provider.models import clear_expired

log = logging.getLogger(__name__)


@shared_task(acks_late=True)
def clear_expired_tokens():
"""Clear expired OAuth2 access, refresh, and ID tokens."""
log.info("Starting clear_expired_tokens...")
clear_expired()
log.info("Finished clear_expired_tokens.")
4 changes: 4 additions & 0 deletions src/ol_social_auth/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ requires-python = ">=3.11"
keywords = ["Python", "edx"]
dependencies = [
"Django>=4.0",
"django-oauth-toolkit",
"social-auth-core>=4.5.4",
]

[project.entry-points."lms.djangoapp"]
ol_social_auth = "ol_social_auth.apps:OLSocialAuthConfig"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Expand Down
26 changes: 26 additions & 0 deletions src/ol_social_auth/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[tool:pytest]
pep8maxlinelength = 119
DJANGO_SETTINGS_MODULE = lms.envs.test
addopts = --nomigrations --reuse-db --durations=20
filterwarnings =
default
ignore::xblock.exceptions.FieldDataDeprecationWarning
ignore::pytest.PytestConfigWarning
ignore:No request passed to the backend, unable to rate-limit:UserWarning
ignore:Flags not at the start of the expression:DeprecationWarning
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc':DeprecationWarning
ignore:invalid escape sequence:DeprecationWarning
ignore:`formatargspec` is deprecated since Python 3.5:DeprecationWarning
ignore:the imp module is deprecated in favour of importlib:DeprecationWarning
ignore:"is" with a literal:SyntaxWarning
ignore:defusedxml.lxml is no longer supported:DeprecationWarning
ignore: `np.int` is a deprecated alias for the builtin `int`.:DeprecationWarning
ignore: `np.float` is a deprecated alias for the builtin `float`.:DeprecationWarning
ignore: `np.complex` is a deprecated alias for the builtin `complex`.:DeprecationWarning
ignore: 'etree' is deprecated. Use 'xml.etree.ElementTree' instead.:DeprecationWarning
ignore: defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead.:DeprecationWarning

junit_family = xunit2
norecursedirs = .* *.egg build conf dist node_modules test_root cms/envs lms/envs
python_classes =
python_files = tests.py test_*.py tests_*.py *_tests.py __init__.py
23 changes: 23 additions & 0 deletions src/ol_social_auth/tests/tasks_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Tests for ol_social_auth tasks."""

from ol_social_auth import tasks


def test_clear_expired_tokens(mocker):
"""Test that clear_expired_tokens calls the clear_expired function."""
patched_clear_expired = mocker.patch("ol_social_auth.tasks.clear_expired")

tasks.clear_expired_tokens.delay()
patched_clear_expired.assert_called_once_with()


def test_clear_expired_tokens_logging(mocker):
"""Test that clear_expired_tokens logs start and finish messages."""
mocker.patch("ol_social_auth.tasks.clear_expired")
patched_log_info = mocker.patch("ol_social_auth.tasks.log.info")

tasks.clear_expired_tokens()

assert patched_log_info.call_count == 2
patched_log_info.assert_any_call("Starting clear_expired_tokens...")
patched_log_info.assert_any_call("Finished clear_expired_tokens.")
Loading