Repository: jazzband/django-oauth-toolkit

Issue:
Token introspection always uses time-zone-aware datetimes 
Hi,

I'm attempting to use the new token introspection and ran into a bit of a snag. Our app is using USE_TZ=False, which results in the following:

```
  File "lib/python3.6/site-packages/rest_framework/views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 477, in dispatch
    self.initial(request, *args, **kwargs)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 394, in initial
    self.perform_authentication(request)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 320, in perform_authentication
    request.user
  File "lib/python3.6/site-packages/rest_framework/request.py", line 381, in __getattribute__
    return super(Request, self).__getattribute__(attr)
  File "lib/python3.6/site-packages/rest_framework/request.py", line 196, in user
    self._authenticate()
  File "lib/python3.6/site-packages/rest_framework/request.py", line 345, in _authenticate
    user_auth_tuple = authenticator.authenticate(self)
  File "lib/python3.6/site-packages/oauth2_provider/contrib/rest_framework/authentication.py", line 18, in authenticate
    valid, r = oauthlib_core.verify_request(request, scopes=[])
  File "lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 168, in verify_request
    valid, r = self.server.verify_request(uri, http_method, body, headers, scopes=scopes)
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 64, in wrapper
    return f(endpoint, uri, *args, **kwargs)
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py", line 75, in verify_request
    return token_type_handler.validate_request(request), request
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py", line 297, in validate_request
    token, request.scopes, request)
  File "lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py", line 340, in validate_bearer_token
    if access_token and access_token.is_valid(scopes):
  File "lib/python3.6/site-packages/oauth2_provider/models.py", line 250, in is_valid
    return not self.is_expired() and self.allow_scopes(scopes)
  File "lib/python3.6/site-packages/oauth2_provider/models.py", line 259, in is_expired
    return timezone.now() >= self.expires
TypeError: can't compare offset-naive and offset-aware datetimes
```
I discovered that OAuth2Validator._get_token_from_authentication_server() is unconditionally calling django.utils.timezone.make_aware() on the expiry time:

https://github.com/evonove/django-oauth-toolkit/blob/fa33444e81b3c95432999d51cdb2acdb98fc16bf/oauth2_provider/oauth2_validators.py#L281-L281

Is there any reason for this?

Thanks

Edit: I think we're just going to turn on USE_TZ. Still, this could definitely be handled more gracefully.
Token introspection always uses time-zone-aware datetimes 
Hi,

I'm attempting to use the new token introspection and ran into a bit of a snag. Our app is using USE_TZ=False, which results in the following:

```
  File "lib/python3.6/site-packages/rest_framework/views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 477, in dispatch
    self.initial(request, *args, **kwargs)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 394, in initial
    self.perform_authentication(request)
  File "lib/python3.6/site-packages/rest_framework/views.py", line 320, in perform_authentication
    request.user
  File "lib/python3.6/site-packages/rest_framework/request.py", line 381, in __getattribute__
    return super(Request, self).__getattribute__(attr)
  File "lib/python3.6/site-packages/rest_framework/request.py", line 196, in user
    self._authenticate()
  File "lib/python3.6/site-packages/rest_framework/request.py", line 345, in _authenticate
    user_auth_tuple = authenticator.authenticate(self)
  File "lib/python3.6/site-packages/oauth2_provider/contrib/rest_framework/authentication.py", line 18, in authenticate
    valid, r = oauthlib_core.verify_request(request, scopes=[])
  File "lib/python3.6/site-packages/oauth2_provider/oauth2_backends.py", line 168, in verify_request
    valid, r = self.server.verify_request(uri, http_method, body, headers, scopes=scopes)
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 64, in wrapper
    return f(endpoint, uri, *args, **kwargs)
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py", line 75, in verify_request
    return token_type_handler.validate_request(request), request
  File "lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py", line 297, in validate_request
    token, request.scopes, request)
  File "lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py", line 340, in validate_bearer_token
    if access_token and access_token.is_valid(scopes):
  File "lib/python3.6/site-packages/oauth2_provider/models.py", line 250, in is_valid
    return not self.is_expired() and self.allow_scopes(scopes)
  File "lib/python3.6/site-packages/oauth2_provider/models.py", line 259, in is_expired
    return timezone.now() >= self.expires
TypeError: can't compare offset-naive and offset-aware datetimes
```
I discovered that OAuth2Validator._get_token_from_authentication_server() is unconditionally calling django.utils.timezone.make_aware() on the expiry time:

https://github.com/evonove/django-oauth-toolkit/blob/fa33444e81b3c95432999d51cdb2acdb98fc16bf/oauth2_provider/oauth2_validators.py#L281-L281

Is there any reason for this?

Thanks

Edit: I think we're just going to turn on USE_TZ. Still, this could definitely be handled more gracefully.


Discussion:
You are right. I'll have a look on it.
Is this being worked on? I would like to give it a try if no one is tackling it.
I completely forgot about it. You are welcome to take care of it.
You are right. I'll have a look on it.
Is this being worked on? I would like to give it a try if no one is tackling it.
I completely forgot about it. You are welcome to take care of it.

Resolving patch:
diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py
--- a/oauth2_provider/oauth2_validators.py
+++ b/oauth2_provider/oauth2_validators.py
@@ -357,7 +357,7 @@ def _get_token_from_authentication_server(
                 expires = max_caching_time
 
             scope = content.get("scope", "")
-            expires = make_aware(expires)
+            expires = make_aware(expires) if settings.USE_TZ else expires
 
             access_token, _created = AccessToken.objects.update_or_create(
                 token=token,


Test patch:
diff --git a/tests/test_introspection_auth.py b/tests/test_introspection_auth.py
--- a/tests/test_introspection_auth.py
+++ b/tests/test_introspection_auth.py
@@ -2,6 +2,7 @@
 import datetime
 
 import pytest
+from django.conf import settings
 from django.conf.urls import include
 from django.contrib.auth import get_user_model
 from django.http import HttpResponse
@@ -12,6 +13,7 @@
 
 from oauth2_provider.models import get_access_token_model, get_application_model
 from oauth2_provider.oauth2_validators import OAuth2Validator
+from oauth2_provider.settings import oauth2_settings
 from oauth2_provider.views import ScopedProtectedResourceView
 
 from . import presets
@@ -154,6 +156,25 @@ def test_get_token_from_authentication_server_existing_token(self, mock_get):
         self.assertEqual(token.user.username, "foo_user")
         self.assertEqual(token.scope, "read write dolphin")
 
+    @mock.patch("requests.post", side_effect=mocked_requests_post)
+    def test_get_token_from_authentication_server_expires_timezone(self, mock_get):
+        """
+        Test method _get_token_from_authentication_server for projects with USE_TZ False
+        """
+        settings_use_tz_backup = settings.USE_TZ
+        settings.USE_TZ = False
+        try:
+            self.validator._get_token_from_authentication_server(
+                "foo",
+                oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL,
+                oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN,
+                oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS,
+            )
+        except ValueError as exception:
+            self.fail(str(exception))
+        finally:
+            settings.USE_TZ = settings_use_tz_backup
+
     @mock.patch("requests.post", side_effect=mocked_requests_post)
     def test_validate_bearer_token(self, mock_get):
         """