This commit is contained in:
mahdavi 2020-08-13 10:06:25 +04:30
parent 94a8e3f25f
commit 46230c3cfe
6 changed files with 106 additions and 20 deletions

View file

@ -25,12 +25,12 @@ class ApplicationAdmin(ApplicationAdmin):
@admin.register(Resource) @admin.register(Resource)
class ResourceAdmin(admin.ModelAdmin): class ResourceAdmin(admin.ModelAdmin):
list_display = ("token", "user", "expires") list_display = ("name", "token", "user", "expires")
@admin.register(Scope) @admin.register(Scope)
class ScopeAdmin(admin.ModelAdmin): class ScopeAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'is_default') list_display = ('name', "resource", 'description', 'is_default')
# admin.site.register(RestrictedApplication, RestrictedApplicationAdmin) # admin.site.register(RestrictedApplication, RestrictedApplicationAdmin)

View file

@ -40,7 +40,7 @@ class Resource(AbstractAccessToken):
application = None application = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) 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( user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True,
@ -55,6 +55,9 @@ class Resource(AbstractAccessToken):
def allow_scopes(self, scopes): def allow_scopes(self, scopes):
return scopes == [self.scope] return scopes == [self.scope]
def __str__(self):
return self.name
class Scope(models.Model): class Scope(models.Model):
""" """
@ -74,6 +77,13 @@ class Scope(models.Model):
help_text='The application to which the scope belongs.', help_text='The application to which the scope belongs.',
related_name='scopes' 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 #: The name of the scope
name = models.CharField( name = models.CharField(
max_length=255, max_length=255,
@ -91,6 +101,23 @@ class Scope(models.Model):
help_text='Indicates if this scope should be included in the default scopes.' 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 @classmethod
def register(cls, name, description, is_default=False): def register(cls, name, description, is_default=False):
""" """

View file

@ -18,16 +18,16 @@ class Scopes(BaseScopes):
def get_queryset(self, application=None): def get_queryset(self, application=None):
queryset = Scope.objects.all() queryset = Scope.objects.all()
if application: if application:
queryset = queryset.filter(name__in=application.allowed_scopes) queryset = queryset.filter(name__in=application.allowed_scopes).order_by('resource__uuid')
return queryset return queryset
def get_all_scopes(self): 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): 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 return scopes
def get_default_scopes(self, application=None, request=None, *args, **kwargs): 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()]

View file

View file

@ -1,22 +1,81 @@
""" import calendar
Django views for the gooyal-dynamic-scopes package.
"""
import json
import functools import functools
import json
from django.conf import settings from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse, HttpResponseForbidden 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.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 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 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): def protected_resource(scopes=None):

View file

@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView
from django.urls import path, include from django.urls import path, include
from django.contrib import admin 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, \ from apps.transactions.views import TransactionList, TransactionDetail, TransactionPay, TransactionReceipt, \
ServiceTransactionVerify, ServiceTransactionSubmit ServiceTransactionVerify, ServiceTransactionSubmit
from apps.users.views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \ from apps.users.views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \