Skip to content

Commit f0091f1

Browse files
authored
fix race condition for AccessToken creation in oauth2_validators (#611)
* work around race condition in which two threads both try to create the same AccessToken using update_or_create instead of two steps go get_or_create then update + save * update changelog
1 parent ad07c99 commit f0091f1

File tree

2 files changed

+28
-43
lines changed

2 files changed

+28
-43
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 1.2.x [unrealeased]
2+
3+
* Fix a race condition in creation of AccessToken with external oauth2 server.
4+
15
### 1.2.0 [2018-06-03]
26

37
* **Compatibility**: Python 3.4 is the new minimum required version.

oauth2_provider/oauth2_validators.py

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -332,20 +332,15 @@ def _get_token_from_authentication_server(
332332
scope = content.get("scope", "")
333333
expires = make_aware(expires)
334334

335-
try:
336-
access_token = AccessToken.objects.select_related("application", "user").get(token=token)
337-
except AccessToken.DoesNotExist:
338-
access_token = AccessToken.objects.create(
339-
token=token,
340-
user=user,
341-
application=None,
342-
scope=scope,
343-
expires=expires
344-
)
345-
else:
346-
access_token.expires = expires
347-
access_token.scope = scope
348-
access_token.save()
335+
access_token, _created = AccessToken\
336+
.objects.select_related("application", "user")\
337+
.update_or_create(token=token,
338+
defaults={
339+
"user": user,
340+
"application": None,
341+
"scope": scope,
342+
"expires": expires,
343+
})
349344

350345
return access_token
351346

@@ -362,43 +357,29 @@ def validate_bearer_token(self, token, scopes, request):
362357

363358
try:
364359
access_token = AccessToken.objects.select_related("application", "user").get(token=token)
365-
# if there is a token but invalid then look up the token
366-
if introspection_url and (introspection_token or introspection_credentials):
367-
if not access_token.is_valid(scopes):
368-
access_token = self._get_token_from_authentication_server(
369-
token,
370-
introspection_url,
371-
introspection_token,
372-
introspection_credentials
373-
)
374-
if access_token and access_token.is_valid(scopes):
375-
request.client = access_token.application
376-
request.user = access_token.user
377-
request.scopes = scopes
378-
379-
# this is needed by django rest framework
380-
request.access_token = access_token
381-
return True
382-
self._set_oauth2_error_on_request(request, access_token, scopes)
383-
return False
384360
except AccessToken.DoesNotExist:
385-
# there is no initial token, look up the token
361+
access_token = None
362+
363+
# if there is no token or it's invalid then introspect the token if there's an external OAuth server
364+
if not access_token or not access_token.is_valid(scopes):
386365
if introspection_url and (introspection_token or introspection_credentials):
387366
access_token = self._get_token_from_authentication_server(
388367
token,
389368
introspection_url,
390369
introspection_token,
391370
introspection_credentials
392371
)
393-
if access_token and access_token.is_valid(scopes):
394-
request.client = access_token.application
395-
request.user = access_token.user
396-
request.scopes = scopes
397-
398-
# this is needed by django rest framework
399-
request.access_token = access_token
400-
return True
401-
self._set_oauth2_error_on_request(request, None, scopes)
372+
373+
if access_token and access_token.is_valid(scopes):
374+
request.client = access_token.application
375+
request.user = access_token.user
376+
request.scopes = scopes
377+
378+
# this is needed by django rest framework
379+
request.access_token = access_token
380+
return True
381+
else:
382+
self._set_oauth2_error_on_request(request, access_token, scopes)
402383
return False
403384

404385
def validate_code(self, client_id, code, client, request, *args, **kwargs):

0 commit comments

Comments
 (0)