From 46230c3cfe7b476394b2460f2a704d6d21d6b787 Mon Sep 17 00:00:00 2001 From: mahdavi Date: Thu, 13 Aug 2020 10:06:25 +0430 Subject: [PATCH] clean up --- apps/gooyal_oauth2/admin.py | 4 +- apps/gooyal_oauth2/models.py | 29 ++++++- apps/gooyal_oauth2/scopes.py | 8 +- apps/gooyal_oauth2/views/__init__.py | 0 .../{views.py => views/introspect.py} | 83 ++++++++++++++++--- gooyal_accounts/urls.py | 2 +- 6 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 apps/gooyal_oauth2/views/__init__.py rename apps/gooyal_oauth2/{views.py => views/introspect.py} (57%) diff --git a/apps/gooyal_oauth2/admin.py b/apps/gooyal_oauth2/admin.py index 76a1e8b..b8263cf 100755 --- a/apps/gooyal_oauth2/admin.py +++ b/apps/gooyal_oauth2/admin.py @@ -25,12 +25,12 @@ class ApplicationAdmin(ApplicationAdmin): @admin.register(Resource) class ResourceAdmin(admin.ModelAdmin): - list_display = ("token", "user", "expires") + list_display = ("name", "token", "user", "expires") @admin.register(Scope) class ScopeAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'is_default') + list_display = ('name', "resource", 'description', 'is_default') # admin.site.register(RestrictedApplication, RestrictedApplicationAdmin) diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py index 5f6ab72..b16d46c 100644 --- a/apps/gooyal_oauth2/models.py +++ b/apps/gooyal_oauth2/models.py @@ -40,7 +40,7 @@ class Resource(AbstractAccessToken): application = None uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) - name = models.CharField(max_length=255, blank=True) + name = models.CharField(max_length=255) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, @@ -55,6 +55,9 @@ class Resource(AbstractAccessToken): def allow_scopes(self, scopes): return scopes == [self.scope] + def __str__(self): + return self.name + class Scope(models.Model): """ @@ -74,6 +77,13 @@ class Scope(models.Model): help_text='The application to which the scope belongs.', related_name='scopes' ) + resource = models.ForeignKey( + Resource, + models.PROTECT, + blank=True, null=True, + help_text='The resource of scope.', + related_name='scopes' + ) #: The name of the scope name = models.CharField( max_length=255, @@ -91,6 +101,23 @@ class Scope(models.Model): help_text='Indicates if this scope should be included in the default scopes.' ) + @property + def final_name(self): + args = [] + if self.resource: + args.append(self.resource.name) + + args.append(self.name) + return '.'.join(args) + + @property + def final_description(self): + resource_name = self.resource and self.resource.name + + if resource_name: + return f"{resource_name} -> {self.description}" + return self.description + @classmethod def register(cls, name, description, is_default=False): """ diff --git a/apps/gooyal_oauth2/scopes.py b/apps/gooyal_oauth2/scopes.py index cf92a0f..d7387b9 100644 --- a/apps/gooyal_oauth2/scopes.py +++ b/apps/gooyal_oauth2/scopes.py @@ -18,16 +18,16 @@ class Scopes(BaseScopes): def get_queryset(self, application=None): queryset = Scope.objects.all() if application: - queryset = queryset.filter(name__in=application.allowed_scopes) + queryset = queryset.filter(name__in=application.allowed_scopes).order_by('resource__uuid') return queryset def get_all_scopes(self): - return {scope.name: scope.description for scope in self.get_queryset().all()} + return {scope.final_name: scope.final_description for scope in self.get_queryset().all()} def get_available_scopes(self, application=None, request=None, *args, **kwargs): - scopes = [scope.name for scope in self.get_queryset(application).all()] + scopes = [scope.final_name for scope in self.get_queryset(application).all()] return scopes def get_default_scopes(self, application=None, request=None, *args, **kwargs): - return [scope.name for scope in self.get_queryset(application).filter(is_default=True).all()] + return [scope.final_name for scope in self.get_queryset(application).filter(is_default=True).all()] diff --git a/apps/gooyal_oauth2/views/__init__.py b/apps/gooyal_oauth2/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/gooyal_oauth2/views.py b/apps/gooyal_oauth2/views/introspect.py similarity index 57% rename from apps/gooyal_oauth2/views.py rename to apps/gooyal_oauth2/views/introspect.py index fcabc0a..adf0bfb 100644 --- a/apps/gooyal_oauth2/views.py +++ b/apps/gooyal_oauth2/views/introspect.py @@ -1,22 +1,81 @@ -""" -Django views for the gooyal-dynamic-scopes package. -""" - -import json +import calendar import functools +import json -from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponse, HttpResponseForbidden +from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_http_methods, require_POST - +from django.views.decorators.http import require_http_methods +from oauth2_provider.models import get_access_token_model +from oauth2_provider.oauth2_backends import OAuthLibCore +from oauth2_provider.views import ClientProtectedScopedResourceView from oauthlib.oauth2 import Server -from oauth2_provider.oauth2_backends import OAuthLibCore -from oauth2_provider.views import IntrospectTokenView - from apps.gooyal_oauth2.validators import IntrospectOAuth2Validator -from .models import Scope + + +@method_decorator(csrf_exempt, name="dispatch") +class IntrospectTokenView(ClientProtectedScopedResourceView): + """ + Implements an endpoint for token introspection based + on RFC 7662 https://tools.ietf.org/html/rfc7662 + + 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.get(token=token_value) + except ObjectDoesNotExist: + return HttpResponse( + content=json.dumps({"active": False}), + status=401, + content_type="application/json" + ) + 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: + data["username"] = token.user.get_username() + return HttpResponse(content=json.dumps(data), status=200, content_type="application/json") + else: + return HttpResponse(content=json.dumps({ + "active": False, + }), status=200, content_type="application/json") + + 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)) def protected_resource(scopes=None): diff --git a/gooyal_accounts/urls.py b/gooyal_accounts/urls.py index db1cba4..209c46c 100644 --- a/gooyal_accounts/urls.py +++ b/gooyal_accounts/urls.py @@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView from django.urls import path, include from django.contrib import admin -from apps.gooyal_oauth2.views import introspect_token +from apps.gooyal_oauth2.views.introspect import introspect_token from apps.transactions.views import TransactionList, TransactionDetail, TransactionPay, TransactionReceipt, \ ServiceTransactionVerify, ServiceTransactionSubmit from apps.users.views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \