diff --git a/accounts/settings.py b/accounts/settings.py index 90514f4..f5e0bf2 100644 --- a/accounts/settings.py +++ b/accounts/settings.py @@ -77,8 +77,11 @@ OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'gooyal_oauth2.RefreshToken' OIDC_RSA_PRIVATE_KEY_FILE_PATH = config('OIDC_RSA_PRIVATE_KEY_FILE_PATH', default=None) -with open(OIDC_RSA_PRIVATE_KEY_FILE_PATH) as oidc_rsa_private_key_file: - OIDC_RSA_PRIVATE_KEY = oidc_rsa_private_key_file.read() +if OIDC_RSA_PRIVATE_KEY_FILE_PATH: + with open(OIDC_RSA_PRIVATE_KEY_FILE_PATH) as oidc_rsa_private_key_file: + OIDC_RSA_PRIVATE_KEY = oidc_rsa_private_key_file.read() +else: + OIDC_RSA_PRIVATE_KEY = None OAUTH2_PROVIDER = { # this is the list of available scopes diff --git a/apps/gooyal_oauth2/migrations/0003_accesstoken_token_checksum_refreshtoken_token_family_and_more.py b/apps/gooyal_oauth2/migrations/0003_accesstoken_token_checksum_refreshtoken_token_family_and_more.py new file mode 100644 index 0000000..3f59a50 --- /dev/null +++ b/apps/gooyal_oauth2/migrations/0003_accesstoken_token_checksum_refreshtoken_token_family_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.4 on 2024-12-30 13:48 + +import oauth2_provider.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gooyal_oauth2', '0002_alter_application_name'), + ] + + operations = [ + migrations.AddField( + model_name='accesstoken', + name='token_checksum', + field=oauth2_provider.models.TokenChecksumField(db_index=True, default='', max_length=64, unique=True), + preserve_default=False, + ), + migrations.AddField( + model_name='refreshtoken', + name='token_family', + field=models.UUIDField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='accesstoken', + name='token', + field=models.TextField(), + ), + migrations.AlterField( + model_name='application', + name='name', + field=models.CharField(max_length=255, unique=True), + ), + ] diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py index 9ccf728..113ad30 100644 --- a/apps/gooyal_oauth2/models.py +++ b/apps/gooyal_oauth2/models.py @@ -1,12 +1,18 @@ import requests -from django.conf import settings -from django.db import models from django.db.models import JSONField from oauth2_provider.models import AbstractApplication, AbstractAccessToken, AbstractGrant, AbstractRefreshToken, \ - AbstractIDToken + AbstractIDToken, get_access_token_model, get_refresh_token_model from oauth2_provider.scopes import get_scopes_backend from oauth2_provider.settings import oauth2_settings import uuid +from contextlib import suppress + +from django.conf import settings +from django.db import models, router, transaction +from django.utils import timezone + +from .settings import oauth2_settings + class Resource(models.Model): @@ -176,6 +182,30 @@ class RefreshToken(AbstractRefreshToken): application = models.ForeignKey( oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens') + # TODO: submit merge request + def revoke(self): + """ + Mark this refresh token revoked and revoke related access token + """ + access_token_model = get_access_token_model() + access_token_database = router.db_for_write(access_token_model) + refresh_token_model = get_refresh_token_model() + + # Use the access_token_database instead of making the assumption it is in 'default'. + with transaction.atomic(using=access_token_database): + token = refresh_token_model.objects.select_for_update().filter(pk=self.pk, revoked__isnull=True) + if not token: + return + self = list(token)[0] + + with suppress(access_token_model.DoesNotExist): + access_token_model.objects.get(pk=self.access_token_id).revoke() + + self.access_token = None + self.revoked = timezone.now() + self.save() + + class AccessToken(AbstractAccessToken): id = None diff --git a/requirements.txt b/requirements.txt index 9239d4f..67a7c08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,27 +4,27 @@ # # pip-compile # -amqp==5.2.0 +amqp==5.3.1 # via kombu asgiref==3.8.1 # via # django # django-cors-headers -async-timeout==4.0.3 +async-timeout==5.0.1 # via redis -attrs==23.2.0 +attrs==24.3.0 # via # jsonschema # referencing -billiard==4.2.0 +billiard==4.2.1 # via celery celery==5.4.0 # via -r requirements.in -certifi==2024.6.2 +certifi==2024.12.14 # via requests -cffi==1.16.0 +cffi==1.17.1 # via cryptography -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 # via requests click==8.1.7 # via @@ -38,11 +38,11 @@ click-plugins==1.1.1 # via celery click-repl==0.3.0 # via celery -crispy-bootstrap5==2024.2 +crispy-bootstrap5==2024.10 # via -r requirements.in -cryptography==42.0.8 +cryptography==44.0.0 # via jwcrypto -django==5.0.6 +django==5.1.4 # via # -r requirements.in # crispy-bootstrap5 @@ -56,31 +56,31 @@ django==5.0.6 # drf-spectacular django-betterforms==2.0.0 # via -r requirements.in -django-cors-headers==4.4.0 +django-cors-headers==4.6.0 # via -r requirements.in -django-crispy-forms==2.2 +django-crispy-forms==2.3 # via # -r requirements.in # crispy-bootstrap5 -django-filter==24.2 +django-filter==24.3 # via -r requirements.in django-jalali-date==1.1.3 # via -r requirements.in -django-oauth-toolkit==2.4.0 +django-oauth-toolkit==3.0.1 # via -r requirements.in djangorestframework==3.15.2 # via # -r requirements.in # drf-spectacular -drf-spectacular==0.27.2 +drf-spectacular==0.28.0 # via -r requirements.in -gevent==24.2.1 +gevent==24.11.1 # via -r requirements.in -greenlet==3.0.3 +greenlet==3.1.1 # via gevent -gunicorn==22.0.0 +gunicorn==23.0.0 # via -r requirements.in -idna==3.7 +idna==3.10 # via requests inflection==0.5.1 # via drf-spectacular @@ -88,27 +88,27 @@ jalali-core==1.0.0 # via jdatetime jdatetime==5.0.0 # via django-jalali-date -jsonschema==4.22.0 +jsonschema==4.23.0 # via drf-spectacular -jsonschema-specifications==2023.12.1 +jsonschema-specifications==2024.10.1 # via jsonschema jwcrypto==1.5.6 # via django-oauth-toolkit -kombu==5.3.7 +kombu==5.4.2 # via celery -markdown==3.6 +markdown==3.7 # via -r requirements.in oauthlib==3.2.2 # via django-oauth-toolkit -packaging==24.1 +packaging==24.2 # via gunicorn -pillow==10.3.0 +pillow==11.0.0 # via -r requirements.in -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via click-repl -psycopg[binary]==3.1.19 +psycopg[binary]==3.2.3 # via -r requirements.in -psycopg-binary==3.1.19 +psycopg-binary==3.2.3 # via psycopg pycparser==2.22 # via cffi @@ -116,11 +116,9 @@ python-dateutil==2.9.0.post0 # via celery python-decouple==3.8 # via -r requirements.in -pytz==2024.1 - # via django-oauth-toolkit -pyyaml==6.0.1 +pyyaml==6.0.2 # via drf-spectacular -redis==5.0.6 +redis==5.2.1 # via -r requirements.in referencing==0.35.1 # via @@ -128,23 +126,25 @@ referencing==0.35.1 # jsonschema-specifications requests==2.32.3 # via django-oauth-toolkit -rpds-py==0.18.1 +rpds-py==0.22.3 # via # jsonschema # referencing -six==1.16.0 +six==1.17.0 # via python-dateutil -sqlparse==0.5.0 +sqlparse==0.5.3 # via django typing-extensions==4.12.2 # via # jwcrypto # psycopg -tzdata==2024.1 - # via celery +tzdata==2024.2 + # via + # celery + # kombu uritemplate==4.1.1 # via drf-spectacular -urllib3==2.2.2 +urllib3==2.2.3 # via requests vine==5.1.0 # via @@ -155,7 +155,7 @@ wcwidth==0.2.13 # via prompt-toolkit zope-event==5.0 # via gevent -zope-interface==6.4.post2 +zope-interface==7.2 # via gevent # The following packages are considered to be unsafe in a requirements file: