diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 1489a8845..0ebcddf03 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -434,12 +434,10 @@ def get_refresh_token_model(): return apps.get_model(oauth2_settings.REFRESH_TOKEN_MODEL) -def clear_expired(): - now = timezone.now() +def get_refresh_expire_at(): + """ Return the datetime before which refresh tokens are expired. """ refresh_expire_at = None - access_token_model = get_access_token_model() - refresh_token_model = get_refresh_token_model() - grant_model = get_grant_model() + REFRESH_TOKEN_EXPIRE_SECONDS = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS if REFRESH_TOKEN_EXPIRE_SECONDS: if not isinstance(REFRESH_TOKEN_EXPIRE_SECONDS, timedelta): @@ -448,7 +446,17 @@ def clear_expired(): 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 + refresh_expire_at = timezone.now() - REFRESH_TOKEN_EXPIRE_SECONDS + + return refresh_expire_at + + +def clear_expired(): + now = timezone.now() + access_token_model = get_access_token_model() + refresh_token_model = get_refresh_token_model() + grant_model = get_grant_model() + refresh_expire_at = get_refresh_expire_at() with transaction.atomic(): if refresh_expire_at: diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index e236f9064..5f5dc17ad 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -2,6 +2,7 @@ import logging from django.contrib.auth.mixins import LoginRequiredMixin +from django.db.models import Q from django.http import HttpResponse from django.utils import timezone from django.utils.decorators import method_decorator @@ -12,7 +13,9 @@ from ..exceptions import OAuthToolkitError from ..forms import AllowForm from ..http import OAuth2ResponseRedirect -from ..models import get_access_token_model, get_application_model, get_refresh_token_model +from ..models import ( + get_access_token_model, get_application_model, + get_refresh_token_model, get_refresh_expire_at) from ..scopes import get_scopes_backend from ..settings import oauth2_settings from ..signals import app_authorized @@ -185,17 +188,24 @@ def get(self, request, *args, **kwargs): return self.redirect(uri, application) elif require_approval == "auto": + now = timezone.now() tokens = get_access_token_model().objects.filter( - user=request.user, - application=kwargs["application"], - expires__gt=timezone.now() - ).all() - - refresh_tokens = get_refresh_token_model().objects.filter( user=request.user, application=kwargs["application"] - ).exclude(revoked__lt=timezone.now()).all() - tokens = list(tokens) + [r.access_token for r in refresh_tokens] + ).filter( + # token is not expired + Q(expires__gt=now) | + # OR refresh_token exists and is not revoked + (Q(refresh_token__isnull=False) & + ~Q(refresh_token__revoked__lt=now)) + ) + + # exclude if refresh token is expired + refresh_expire_at = get_refresh_expire_at() + if refresh_expire_at: + tokens = tokens.exclude( + Q(refresh_token__created__lt=refresh_expire_at) + ) # check past authorizations regarded the same scopes as the current one for token in tokens: diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index 45116dad6..62f6ba94c 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -185,6 +185,18 @@ def test_pre_auth_approval_prompt(self): expires=timezone.now() + datetime.timedelta(days=1), scope="read write" ) + reftok = RefreshToken.objects.create( + user=self.test_user, token="0123456789", + application=self.application, + access_token=tok, + created=timezone.now() - datetime.timedelta(seconds=2000) + ) + + # WIP: This fails, reporting no related object. Why? + # I think new tests are failing due to this issue. @madprime + tok = AccessToken.objects.get(id=tok.id) + print(tok.refresh_token) + self.client.login(username="test_user", password="123456") query_string = urlencode({ "client_id": self.application.client_id, @@ -197,19 +209,25 @@ def test_pre_auth_approval_prompt(self): url = "{url}?{qs}".format(url=reverse("oauth2_provider:authorize"), qs=query_string) response = self.client.get(url) self.assertEqual(response.status_code, 302) + # user already authorized the application, but with different scopes: prompt them. + tok.scope = "read" + tok.save() + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + tok.scope = "read write" # access token expired but valid refresh token exists tok.expires = timezone.now() - datetime.timedelta(days=1) tok.save() - reftok = RefreshToken.objects.create( - user=self.test_user, token="0123456789", - application=self.application, - access_token=tok - ) response = self.client.get(url) self.assertEqual(response.status_code, 302) - # user already authorized the application, but with different scopes: prompt them. - tok.scope = "read" - tok.save() + # refresh token invalid due to expiration + oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS = 1000 + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS = None + # refresh token revoked + reftok.revoked = timezone.now() - datetime.timedelta(seconds=10) + reftok.save() response = self.client.get(url) self.assertEqual(response.status_code, 200)