Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feat/verify token exists before saving #1058

2 changes: 1 addition & 1 deletion AUTHORS
Expand Up @@ -64,4 +64,4 @@ pySilver
Shaheed Haque
Andrea Greco
Vinay Karanam

Wagner de Lima
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
* Only saves AccessToken and RefreshToken -- at OAuth2Validator level -- if they do not exist in database or if they are
expired or revoked.

## [1.6.1] 2021-12-23

### Changed
Expand Down
9 changes: 9 additions & 0 deletions oauth2_provider/models.py
Expand Up @@ -453,6 +453,15 @@ def revoke(self):
self.revoked = timezone.now()
self.save()

def is_expired(self):
"""
Check token expiration with timezone awareness
"""
if not self.revoked:
return False

return timezone.now() >= self.revoked

def __str__(self):
return self.token

Expand Down
55 changes: 45 additions & 10 deletions oauth2_provider/oauth2_validators.py
Expand Up @@ -606,16 +606,31 @@ def _create_access_token(self, expires, request, token, source_refresh_token=Non
id_token = token.get("id_token", None)
if id_token:
id_token = self._load_id_token(id_token)
return AccessToken.objects.create(
user=request.user,
scope=token["scope"],
expires=expires,
token=token["access_token"],
id_token=id_token,
application=request.client,
source_refresh_token=source_refresh_token,

access_token = (
AccessToken.objects.select_for_update()
.filter(
user=request.user,
application=request.client,
)
.last()
)

# if there is no access token in the database for a specific user,
# or there is an access token but it is expired, a new access token is created.
if access_token is None or (access_token and access_token.is_expired()):
return AccessToken.objects.create(
user=request.user,
scope=token["scope"],
expires=expires,
token=token["access_token"],
id_token=id_token,
application=request.client,
source_refresh_token=source_refresh_token,
)
# if access token exists and it is not expired.
return access_token

def _create_authorization_code(self, request, code, expires=None):
if not expires:
expires = timezone.now() + timedelta(seconds=oauth2_settings.AUTHORIZATION_CODE_EXPIRE_SECONDS)
Expand All @@ -633,10 +648,30 @@ def _create_authorization_code(self, request, code, expires=None):
)

def _create_refresh_token(self, request, refresh_token_code, access_token):
return RefreshToken.objects.create(
user=request.user, token=refresh_token_code, application=request.client, access_token=access_token
refresh_token = (
RefreshToken.objects.select_for_update()
.filter(user=request.user, application=request.client, access_token=access_token)
.last()
)

# if there is no refresh_token associated with a user and application,
# or there is a refresh token but it's revoked, a new refresh token is created.
if refresh_token is None or (refresh_token and refresh_token.is_expired()):
# as RefreshToken model has a OneToOneField to Access Token,
# is an access token linked to a refresh token is valid, but the refresh token
# is revoked, the refresh token is deleted and a new refresh token is created
# with the still valid access token.
if refresh_token:
refresh_token.delete()

return RefreshToken.objects.create(
user=request.user,
token=refresh_token_code,
application=request.client,
access_token=access_token,
)
return refresh_token

def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
"""
Revoke an access or refresh token.
Expand Down