From 61878794a6dc8b7c6e2736b7670aabd070273bf4 Mon Sep 17 00:00:00 2001 From: mahdavi Date: Wed, 3 Jul 2024 13:44:18 +0330 Subject: [PATCH] uuid in introspection --- accounts/urls.py | 4 +- apps/gooyal_oauth2/signals.py | 2 +- apps/gooyal_oauth2/urls.py | 49 ++++++++++++++++++ apps/gooyal_oauth2/views/introspect.py | 71 ++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 apps/gooyal_oauth2/urls.py create mode 100644 apps/gooyal_oauth2/views/introspect.py diff --git a/accounts/urls.py b/accounts/urls.py index 54ddd9b..7564840 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -33,9 +33,7 @@ urlpatterns = [ path('admin/', admin.site.urls), path('users/', include('apps.users.urls')), - path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')), - - + path('oauth2/', include('apps.gooyal_oauth2.urls', namespace='gooyal_oauth2')), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/apps/gooyal_oauth2/signals.py b/apps/gooyal_oauth2/signals.py index 3f2af94..15d3638 100644 --- a/apps/gooyal_oauth2/signals.py +++ b/apps/gooyal_oauth2/signals.py @@ -17,7 +17,7 @@ def register_scopes(app_config, verbosity=2, interactive=True, **kwargs): The signal is connected in ``apps.py``. """ - #  Register any scopes in the oauth2_provider settings + # Register any scopes in the oauth2_provider settings for name, description in oauth2_settings.SCOPES.items(): Scope.register( name, diff --git a/apps/gooyal_oauth2/urls.py b/apps/gooyal_oauth2/urls.py new file mode 100644 index 0000000..a041ab7 --- /dev/null +++ b/apps/gooyal_oauth2/urls.py @@ -0,0 +1,49 @@ +from django.urls import re_path + +from oauth2_provider import views +from .views.introspect import IntrospectTokenView + +app_name = "oauth2_provider" + + +base_urlpatterns = [ + re_path(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"), + re_path(r"^token/$", views.TokenView.as_view(), name="token"), + re_path(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"), + re_path(r"^introspect/$", IntrospectTokenView.as_view(), name="introspect"), +] + + +management_urlpatterns = [ + # Application management views + re_path(r"^applications/$", views.ApplicationList.as_view(), name="list"), + re_path(r"^applications/register/$", views.ApplicationRegistration.as_view(), name="register"), + re_path(r"^applications/(?P[\w-]+)/$", views.ApplicationDetail.as_view(), name="detail"), + re_path(r"^applications/(?P[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"), + re_path(r"^applications/(?P[\w-]+)/update/$", views.ApplicationUpdate.as_view(), name="update"), + # Token management views + re_path(r"^authorized_tokens/$", views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), + re_path( + r"^authorized_tokens/(?P[\w-]+)/delete/$", + views.AuthorizedTokenDeleteView.as_view(), + name="authorized-token-delete", + ), +] + +oidc_urlpatterns = [ + # .well-known/openid-configuration/ is deprecated + # https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + # does not specify a trailing slash + # Support for trailing slash shall be removed in a future release. + re_path( + r"^\.well-known/openid-configuration/?$", + views.ConnectDiscoveryInfoView.as_view(), + name="oidc-connect-discovery-info", + ), + re_path(r"^\.well-known/jwks.json$", views.JwksInfoView.as_view(), name="jwks-info"), + re_path(r"^userinfo/$", views.UserInfoView.as_view(), name="user-info"), + re_path(r"^logout/$", views.RPInitiatedLogoutView.as_view(), name="rp-initiated-logout"), +] + + +urlpatterns = base_urlpatterns + management_urlpatterns + oidc_urlpatterns diff --git a/apps/gooyal_oauth2/views/introspect.py b/apps/gooyal_oauth2/views/introspect.py new file mode 100644 index 0000000..d911fd6 --- /dev/null +++ b/apps/gooyal_oauth2/views/introspect.py @@ -0,0 +1,71 @@ +import calendar + +from django.core.exceptions import ObjectDoesNotExist +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt + +from oauth2_provider.models import get_access_token_model +from oauth2_provider.views.generic import ClientProtectedScopedResourceView + + +@method_decorator(csrf_exempt, name="dispatch") +class IntrospectTokenView(ClientProtectedScopedResourceView): + """ + Implements an endpoint for token introspection based + on RFC 7662 https://rfc-editor.org/rfc/rfc7662.html + + To access this view the request must pass a OAuth2 Bearer Token + which is allowed to access the scope `introspection`. + """ + + required_scopes = ["introspection"] + + @staticmethod + def get_token_response(token_value=None): + try: + token = ( + get_access_token_model().objects.select_related("user", "application").get(token=token_value) + ) + except ObjectDoesNotExist: + return JsonResponse({"active": False}, status=200) + else: + if token.is_valid(): + data = { + "active": True, + "scope": token.scope, + "exp": int(calendar.timegm(token.expires.timetuple())), + } + if token.application: + data["client_id"] = token.application.client_id + if token.user: + + # NOTICE: i pass uuid instead of username + data["username"] = token.user.pk + return JsonResponse(data) + else: + return JsonResponse({"active": False}, status=200) + + def get(self, request, *args, **kwargs): + """ + Get the token from the URL parameters. + URL: https://example.com/introspect?token=mF_9.B5f-4.1JqM + + :param request: + :param args: + :param kwargs: + :return: + """ + return self.get_token_response(request.GET.get("token", None)) + + def post(self, request, *args, **kwargs): + """ + Get the token from the body form parameters. + Body: token=mF_9.B5f-4.1JqM + + :param request: + :param args: + :param kwargs: + :return: + """ + return self.get_token_response(request.POST.get("token", None))