From 7ed2d863470661b21e3d3572a21bf3645d700d29 Mon Sep 17 00:00:00 2001 From: trbs Date: Mon, 18 May 2015 19:55:47 +0200 Subject: [PATCH 1/3] Issue #148 cleanup of expired tokens Simple implementation of cleanup strategy for expired tokens. --- oauth2_provider/management/__init__.py | 0 oauth2_provider/management/commands/__init__.py | 0 .../management/commands/cleartokens.py | 9 +++++++++ oauth2_provider/models.py | 17 ++++++++++++++++- oauth2_provider/settings.py | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 oauth2_provider/management/__init__.py create mode 100644 oauth2_provider/management/commands/__init__.py create mode 100644 oauth2_provider/management/commands/cleartokens.py diff --git a/oauth2_provider/management/__init__.py b/oauth2_provider/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/oauth2_provider/management/commands/__init__.py b/oauth2_provider/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/oauth2_provider/management/commands/cleartokens.py b/oauth2_provider/management/commands/cleartokens.py new file mode 100644 index 000000000..5b56d2bc1 --- /dev/null +++ b/oauth2_provider/management/commands/cleartokens.py @@ -0,0 +1,9 @@ +from django.core.management.base import BaseCommand, CommandError +from ...models import clear_expired + + +class Command(BaseCommand): + help = "Can be run as a cronjob or directly to clean out expired tokens" + + def handle(self, *args, **options): + clear_expired() diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index d3091fd2a..cc3a3ef01 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals +from datetime import timedelta + from django.core.urlresolvers import reverse -from django.db import models +from django.db import models, transaction from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -253,3 +255,16 @@ def get_application_model(): e = "APPLICATION_MODEL refers to model {0} that has not been installed" raise ImproperlyConfigured(e.format(oauth2_settings.APPLICATION_MODEL)) return app_model + + +def clear_expired(): + REFRESH_TOKEN_EXPIRE_SECONDS = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS + if not isinstance(REFRESH_TOKEN_EXPIRE_SECONDS, timedelta): + REFRESH_TOKEN_EXPIRE_SECONDS = timedelta(seconds=REFRESH_TOKEN_EXPIRE_SECONDS) + now = timezone.now() + with transaction.atomic(): + if REFRESH_TOKEN_EXPIRE_SECONDS: + refresh_expire_date = now - REFRESH_TOKEN_EXPIRE_SECONDS + RefreshToken.objects.filter(access_token__expires__lt=refresh_expire_date).delete() + AccessToken.objects.filter(refresh_token__isnull=True, expires__lt=now).delete() + Grant.objects.filter(expires__lt=now).delete() diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index db5768686..fabda4fc7 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -40,6 +40,7 @@ 'WRITE_SCOPE': 'write', 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60, 'ACCESS_TOKEN_EXPIRE_SECONDS': 36000, + 'REFRESH_TOKEN_EXPIRE_SECONDS': None, 'APPLICATION_MODEL': getattr(settings, 'OAUTH2_PROVIDER_APPLICATION_MODEL', 'oauth2_provider.Application'), 'REQUEST_APPROVAL_PROMPT': 'force', 'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'], From 5ea1e560e02a98ece3bb5be78954fb9783cd44cc Mon Sep 17 00:00:00 2001 From: trbs Date: Sun, 1 Nov 2015 23:08:48 +0100 Subject: [PATCH 2/3] improve variable checking in clear_expired --- oauth2_provider/models.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index cc3a3ef01..3488928a0 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -258,13 +258,21 @@ def get_application_model(): def clear_expired(): - REFRESH_TOKEN_EXPIRE_SECONDS = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS - if not isinstance(REFRESH_TOKEN_EXPIRE_SECONDS, timedelta): - REFRESH_TOKEN_EXPIRE_SECONDS = timedelta(seconds=REFRESH_TOKEN_EXPIRE_SECONDS) now = timezone.now() + refresh_expire_at = None + + REFRESH_TOKEN_EXPIRE_SECONDS = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS + if REFRESH_TOKEN_EXPIRE_SECONDS: + if not isinstance(REFRESH_TOKEN_EXPIRE_SECONDS, timedelta): + try: + REFRESH_TOKEN_EXPIRE_SECONDS = timedelta(seconds=REFRESH_TOKEN_EXPIRE_SECONDS) + except TypeError: + e = "REFRESH_TOKEN_EXPIRE_SECONDS must be either a timedelta or seconds" + raise ImproperlyConfigured(e) + refresh_expire_at = now - REFRESH_TOKEN_EXPIRE_SECONDS + with transaction.atomic(): - if REFRESH_TOKEN_EXPIRE_SECONDS: - refresh_expire_date = now - REFRESH_TOKEN_EXPIRE_SECONDS - RefreshToken.objects.filter(access_token__expires__lt=refresh_expire_date).delete() + if refresh_expire_at: + RefreshToken.objects.filter(access_token__expires__lt=refresh_expire_at).delete() AccessToken.objects.filter(refresh_token__isnull=True, expires__lt=now).delete() Grant.objects.filter(expires__lt=now).delete() From d438c18b57415f754f932c28e9b60ca0c4bef656 Mon Sep 17 00:00:00 2001 From: trbs Date: Sun, 1 Nov 2015 23:09:15 +0100 Subject: [PATCH 3/3] add documentation for refresh_token_expire_seconds --- AUTHORS | 1 + docs/settings.rst | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/AUTHORS b/AUTHORS index 8fa585f0d..edaf09330 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,3 +13,4 @@ David Fischer Ash Christopher Rodney Richardson Hiroki Kiyohara +Bas van Oostveen \ No newline at end of file diff --git a/docs/settings.rst b/docs/settings.rst index 6fc46da46..92a1e47de 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -77,6 +77,19 @@ READ_SCOPE ~~~~~~~~~~ The name of the *read* scope. +REFRESH_TOKEN_EXPIRE_SECONDS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The number of seconds before a refresh token gets removed from the database by +the ``cleartokens``` management command. It's important that ``cleartokens`` +runs regularly (eg: via cron) in order for this setting to work. + +If ``cleartokens`` runs daily the maximum delay before a refresh token is +removed is ``REFRESH_TOKEN_EXPIRE_SECONDS`` + 1 day. This is normally not a +problem since refresh tokens are long lived. + +Note: Refresh tokens need to expire before AccessTokens can be removed from the +database. Using ``cleartokens`` without ``REFRESH_TOKEN_EXPIRE_SECONDS`` has limited effect. + WRITE_SCOPE ~~~~~~~~~~~ The name of the *write* scope.