Skip to content

Commit a36e65f

Browse files
Expect the remote exp to be defined in time zone UTC conform rfc (Fixes #1291)
1 parent f730b64 commit a36e65f

File tree

4 files changed

+111
-14
lines changed

4 files changed

+111
-14
lines changed

oauth2_provider/oauth2_validators.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
)
3838
from .scopes import get_scopes_backend
3939
from .settings import oauth2_settings
40+
from .utils import get_timezone
4041

4142

4243
log = logging.getLogger("oauth2_provider")
@@ -388,7 +389,11 @@ def _get_token_from_authentication_server(
388389
expires = max_caching_time
389390

390391
scope = content.get("scope", "")
391-
expires = make_aware(expires) if settings.USE_TZ else expires
392+
393+
if settings.USE_TZ:
394+
expires = make_aware(
395+
expires, timezone=get_timezone(oauth2_settings.AUTHENTICATION_SERVER_EXP_TIME_ZONE)
396+
)
392397

393398
access_token, _created = AccessToken.objects.update_or_create(
394399
token=token,

oauth2_provider/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
"RESOURCE_SERVER_AUTH_TOKEN": None,
102102
"RESOURCE_SERVER_INTROSPECTION_CREDENTIALS": None,
103103
"RESOURCE_SERVER_TOKEN_CACHING_SECONDS": 36000,
104+
# Authentication Server Exp Timezone: the time zone use dby Auth Server for generate EXP
105+
"AUTHENTICATION_SERVER_EXP_TIME_ZONE": "UTC",
104106
# Whether or not PKCE is required
105107
"PKCE_REQUIRED": True,
106108
# Whether to re-create OAuthlibCore on every request.

oauth2_provider/utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22

3+
from django.conf import settings
34
from jwcrypto import jwk
45

56

@@ -10,3 +11,24 @@ def jwk_from_pem(pem_string):
1011
Converting from PEM is expensive for large keys such as those using RSA.
1112
"""
1213
return jwk.JWK.from_pem(pem_string.encode("utf-8"))
14+
15+
16+
@functools.lru_cache
17+
def get_timezone(time_zone):
18+
"""
19+
Return the default time zone as a tzinfo instance.
20+
21+
This is the time zone defined by settings.TIME_ZONE.
22+
"""
23+
try:
24+
import zoneinfo
25+
except ImportError:
26+
import pytz
27+
28+
return pytz.timezone(time_zone)
29+
else:
30+
if getattr(settings, "USE_DEPRECATED_PYTZ", False):
31+
import pytz
32+
33+
return pytz.timezone(time_zone)
34+
return zoneinfo.ZoneInfo(time_zone)

tests/test_introspection_auth.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
AccessToken = get_access_token_model()
3030
UserModel = get_user_model()
3131

32-
exp = datetime.datetime.now() + datetime.timedelta(days=1)
32+
default_exp = datetime.datetime.now() + datetime.timedelta(days=1)
3333

3434

3535
class ScopeResourceView(ScopedProtectedResourceView):
@@ -42,27 +42,28 @@ def post(self, request, *args, **kwargs):
4242
return HttpResponse("This is a protected resource", 200)
4343

4444

45+
class MockResponse:
46+
def __init__(self, json_data, status_code):
47+
self.json_data = json_data
48+
self.status_code = status_code
49+
50+
def json(self):
51+
return self.json_data
52+
53+
4554
def mocked_requests_post(url, data, *args, **kwargs):
4655
"""
4756
Mock the response from the authentication server
4857
"""
4958

50-
class MockResponse:
51-
def __init__(self, json_data, status_code):
52-
self.json_data = json_data
53-
self.status_code = status_code
54-
55-
def json(self):
56-
return self.json_data
57-
5859
if "token" in data and data["token"] and data["token"] != "12345678900":
5960
return MockResponse(
6061
{
6162
"active": True,
6263
"scope": "read write dolphin",
6364
"client_id": "client_id_{}".format(data["token"]),
6465
"username": "{}_user".format(data["token"]),
65-
"exp": int(calendar.timegm(exp.timetuple())),
66+
"exp": int(calendar.timegm(default_exp.timetuple())),
6667
},
6768
200,
6869
)
@@ -75,6 +76,21 @@ def json(self):
7576
)
7677

7778

79+
def mocked_introspect_request_short_living_token(url, data, *args, **kwargs):
80+
exp = datetime.datetime.now() + datetime.timedelta(minutes=30)
81+
82+
return MockResponse(
83+
{
84+
"active": True,
85+
"scope": "read write dolphin",
86+
"client_id": "client_id_{}".format(data["token"]),
87+
"username": "{}_user".format(data["token"]),
88+
"exp": int(calendar.timegm(exp.timetuple())),
89+
},
90+
200,
91+
)
92+
93+
7894
urlpatterns = [
7995
path("oauth2/", include("oauth2_provider.urls")),
8096
path("oauth2-test-resource/", ScopeResourceView.as_view()),
@@ -156,24 +172,76 @@ def test_get_token_from_authentication_server_existing_token(self, mock_get):
156172
self.assertEqual(token.user.username, "foo_user")
157173
self.assertEqual(token.scope, "read write dolphin")
158174

159-
@mock.patch("requests.post", side_effect=mocked_requests_post)
160-
def test_get_token_from_authentication_server_expires_timezone(self, mock_get):
175+
@mock.patch("requests.post", side_effect=mocked_introspect_request_short_living_token)
176+
def test_get_token_from_authentication_server_expires_no_timezone(self, mock_get):
161177
"""
162178
Test method _get_token_from_authentication_server for projects with USE_TZ False
163179
"""
164180
settings_use_tz_backup = settings.USE_TZ
165181
settings.USE_TZ = False
166182
try:
167-
self.validator._get_token_from_authentication_server(
183+
access_token = self.validator._get_token_from_authentication_server(
184+
"foo",
185+
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL,
186+
oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN,
187+
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS,
188+
)
189+
190+
self.assertFalse(access_token.is_expired())
191+
except ValueError as exception:
192+
self.fail(str(exception))
193+
finally:
194+
settings.USE_TZ = settings_use_tz_backup
195+
196+
@mock.patch("requests.post", side_effect=mocked_introspect_request_short_living_token)
197+
def test_get_token_from_authentication_server_expires_utc_timezone(self, mock_get):
198+
"""
199+
Test method _get_token_from_authentication_server for projects with USE_TZ True and a UTC Timezone
200+
"""
201+
settings_use_tz_backup = settings.USE_TZ
202+
settings_time_zone_backup = settings.TIME_ZONE
203+
settings.USE_TZ = True
204+
settings.TIME_ZONE = "UTC"
205+
try:
206+
access_token = self.validator._get_token_from_authentication_server(
168207
"foo",
169208
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL,
170209
oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN,
171210
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS,
172211
)
212+
213+
self.assertFalse(access_token.is_expired())
214+
except ValueError as exception:
215+
self.fail(str(exception))
216+
finally:
217+
settings.USE_TZ = settings_use_tz_backup
218+
settings.TIME_ZONE = settings_time_zone_backup
219+
220+
@mock.patch("requests.post", side_effect=mocked_introspect_request_short_living_token)
221+
def test_get_token_from_authentication_server_expires_non_utc_timezone(self, mock_get):
222+
"""
223+
Test method _get_token_from_authentication_server for projects with USE_TZ True and a non UTC Timezone
224+
225+
This test is important to check if the UTC Exp. date gets converted correctly
226+
"""
227+
settings_use_tz_backup = settings.USE_TZ
228+
settings_time_zone_backup = settings.TIME_ZONE
229+
settings.USE_TZ = True
230+
settings.TIME_ZONE = "Europe/Amsterdam"
231+
try:
232+
access_token = self.validator._get_token_from_authentication_server(
233+
"foo",
234+
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL,
235+
oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN,
236+
oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS,
237+
)
238+
239+
self.assertFalse(access_token.is_expired())
173240
except ValueError as exception:
174241
self.fail(str(exception))
175242
finally:
176243
settings.USE_TZ = settings_use_tz_backup
244+
settings.TIME_ZONE = settings_time_zone_backup
177245

178246
@mock.patch("requests.post", side_effect=mocked_requests_post)
179247
def test_validate_bearer_token(self, mock_get):

0 commit comments

Comments
 (0)