From f5911e8df228b35c783d5f713ae328fb609de0cb Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:21:57 +0100 Subject: [PATCH 1/8] feat: add is_expired method to RefreshToken model. --- oauth2_provider/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index d7b767a78..2ffa3579c 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -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 From 825f0f9085dff070244776927be99262fb9b56f8 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:23:21 +0100 Subject: [PATCH 2/8] feat: verify is refresh and access token exist and are valid before creating them. --- oauth2_provider/oauth2_validators.py | 49 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 461c40d53..efffb2e54 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -606,15 +606,26 @@ 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( + + access_token = AccessToken.objects.select_for_update().filter( 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, - ) + ).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: @@ -633,9 +644,27 @@ 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. + 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): """ From f912f4b29bb312e2aa1657404892884d82257a69 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:26:56 +0100 Subject: [PATCH 3/8] format: black formatting. --- oauth2_provider/oauth2_validators.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index efffb2e54..fec6d9c3e 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -644,11 +644,11 @@ def _create_authorization_code(self, request, code, expires=None): ) def _create_refresh_token(self, request, refresh_token_code, access_token): - refresh_token = RefreshToken.objects.select_for_update().filter( - user=request.user, - application=request.client, - access_token=access_token - ).last() + 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. From 9e09bac6504ff50e0f40be820624df9a01561429 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:28:12 +0100 Subject: [PATCH 4/8] format: black formatting at access token. --- oauth2_provider/oauth2_validators.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index fec6d9c3e..e465d585c 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -607,10 +607,11 @@ def _create_access_token(self, expires, request, token, source_refresh_token=Non if id_token: id_token = self._load_id_token(id_token) - access_token = AccessToken.objects.select_for_update().filter( - user=request.user, - application=request.client, - ).last() + 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. From 45b95e55d9653f95b410da3d03d09790b68ce841 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:35:17 +0100 Subject: [PATCH 5/8] docs: add changes to CHANGELOG.md. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f10cdf145..56ffc7fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 76e47fc92c22726eb1673e5d29f79d002dbd0b79 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 18:38:08 +0100 Subject: [PATCH 6/8] docs: add developer name to AUTHORS. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 8675dd995..9be3a149d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -64,4 +64,4 @@ pySilver Shaheed Haque Andrea Greco Vinay Karanam - +Wagner de Lima From b0fa93f93119d675aa84a41e953e7d0202dfc980 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Sun, 26 Dec 2021 22:12:49 +0100 Subject: [PATCH 7/8] fix: add check for when the refresh_token is None. --- oauth2_provider/oauth2_validators.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index e465d585c..6ed0b55ac 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -658,7 +658,9 @@ def _create_refresh_token(self, request, refresh_token_code, 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. - refresh_token.delete() + if refresh_token: + refresh_token.delete() + return RefreshToken.objects.create( user=request.user, token=refresh_token_code, From 1eb9ccbc83aafa1c27b65dd279e14c1f34512254 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Dec 2021 21:13:56 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/oauth2_validators.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 6ed0b55ac..95c456da1 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -609,7 +609,10 @@ def _create_access_token(self, expires, request, token, source_refresh_token=Non access_token = ( AccessToken.objects.select_for_update() - .filter(user=request.user, application=request.client,) + .filter( + user=request.user, + application=request.client, + ) .last() )