diff --git a/.gitignore b/.gitignore index eaf3a0b..a6cbe16 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ delme*.py static *.pyc .env +/venv/ diff --git a/apps/gooyal_oauth2/admin.py b/apps/gooyal_oauth2/admin.py index b8263cf..60d341d 100755 --- a/apps/gooyal_oauth2/admin.py +++ b/apps/gooyal_oauth2/admin.py @@ -25,7 +25,7 @@ class ApplicationAdmin(ApplicationAdmin): @admin.register(Resource) class ResourceAdmin(admin.ModelAdmin): - list_display = ("name", "token", "user", "expires") + list_display = ("name", "user", "expires") @admin.register(Scope) diff --git a/apps/gooyal_oauth2/migrations/0006_auto_20200825_0615.py b/apps/gooyal_oauth2/migrations/0006_auto_20200825_0615.py new file mode 100644 index 0000000..37d5a9f --- /dev/null +++ b/apps/gooyal_oauth2/migrations/0006_auto_20200825_0615.py @@ -0,0 +1,48 @@ +# Generated by Django 3.0.8 on 2020-08-25 06:15 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gooyal_oauth2', '0005_auto_20200712_1219'), + ] + + operations = [ + migrations.RemoveField( + model_name='resource', + name='created', + ), + migrations.RemoveField( + model_name='resource', + name='token', + ), + migrations.RemoveField( + model_name='resource', + name='updated', + ), + migrations.AddField( + model_name='application', + name='resource', + field=models.OneToOneField(blank=True, help_text='The resource of application.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='application', to='gooyal_oauth2.Resource'), + ), + migrations.AddField( + model_name='scope', + name='resource', + field=models.ForeignKey(blank=True, help_text='The resource of scope.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='scopes', to='gooyal_oauth2.Resource'), + ), + migrations.AlterField( + model_name='application', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='gooyal_oauth2_application', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='resource', + name='name', + field=models.CharField(max_length=255), + ), + ] diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py index b16d46c..3e31ef5 100644 --- a/apps/gooyal_oauth2/models.py +++ b/apps/gooyal_oauth2/models.py @@ -12,6 +12,20 @@ from oauth2_provider.settings import oauth2_settings import uuid +class Resource(models.Model): + uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) + name = models.CharField(max_length=255) + + user = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, + related_name="resources" + ) + expires = models.DateTimeField() + + def __str__(self): + return self.name + + class Application(AbstractApplication): """ Application model for use with Django OAuth Toolkit that allows the scopes @@ -23,6 +37,13 @@ class Application(AbstractApplication): on_delete=models.PROTECT ) allowed_scope = models.TextField(blank=True) + resource = models.OneToOneField( + Resource, + models.PROTECT, + blank=True, null=True, + help_text='The resource of application.', + related_name='application' + ) @property def allowed_scopes(self): @@ -34,45 +55,20 @@ class Application(AbstractApplication): return app_scopes.intersection(all_scopes) -class Resource(AbstractAccessToken): - source_refresh_token = None - id = None - application = None - - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) - name = models.CharField(max_length=255) - - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, - related_name="resources" - ) - expires = models.DateTimeField() - token = models.CharField(max_length=255, unique=True, ) # introspect token - - scope = 'introspection' - scopes = {'introspection': 'Introspect token scope'} - - def allow_scopes(self, scopes): - return scopes == [self.scope] - - def __str__(self): - return self.name - - class Scope(models.Model): """ Django model for an OAuth scope. """ #: The application that created the scope - #  NOTE: This is not used to limit access to the scope in any way - we want the + # NOTE: This is not used to limit access to the scope in any way - we want the # scope to be available to other applications in order to request access - #   to the resource it protects! + # to the resource it protects! application = models.ForeignKey( oauth2_settings.APPLICATION_MODEL, models.CASCADE, - #  This field is nullable because it is only set for scopes created by - #  external resource servers, which have a corresponding OAuth application - #  record on the authorisation server + # This field is nullable because it is only set for scopes created by + # external resource servers, which have a corresponding OAuth application + # record on the authorisation server blank=True, null=True, help_text='The application to which the scope belongs.', related_name='scopes' diff --git a/apps/gooyal_oauth2/validators.py b/apps/gooyal_oauth2/validators.py index a27a18c..687b9bf 100755 --- a/apps/gooyal_oauth2/validators.py +++ b/apps/gooyal_oauth2/validators.py @@ -41,41 +41,41 @@ class MultiGatewayOAuth2Validator(OAuth2Validator): # pylint: disable=w0223 return False -class IntrospectOAuth2Validator(OAuth2Validator): - def validate_bearer_token(self, token, scopes, request): - """ - When users try to access resources, check that provided token is valid - """ - if not token: - return False - - introspection_url = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL - introspection_token = oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN - introspection_credentials = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS - - try: - access_token = Resource.objects.select_related("user").get(token=token) - except Resource.DoesNotExist: - access_token = None - - # if there is no token or it's invalid then introspect the token if there's an external OAuth server - if not access_token or not access_token.is_valid(scopes): - if introspection_url and (introspection_token or introspection_credentials): - access_token = self._get_token_from_authentication_server( - token, - introspection_url, - introspection_token, - introspection_credentials - ) - - if access_token and access_token.is_valid(scopes): - request.client = access_token.application - request.user = access_token.user or (access_token.application and access_token.application.user) - request.scopes = scopes - - # this is needed by django rest framework - request.access_token = access_token - return True - else: - self._set_oauth2_error_on_request(request, access_token, scopes) - return False +# class IntrospectOAuth2Validator(OAuth2Validator): +# def validate_bearer_token(self, token, scopes, request): +# """ +# When users try to access resources, check that provided token is valid +# """ +# if not token: +# return False +# +# introspection_url = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL +# introspection_token = oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN +# introspection_credentials = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS +# +# try: +# access_token = AccessToken.objects.select_related("application", "user").get(token=token) +# except AccessToken.DoesNotExist: +# access_token = None +# +# # if there is no token or it's invalid then introspect the token if there's an external OAuth server +# if not access_token or not access_token.is_valid(scopes): +# if introspection_url and (introspection_token or introspection_credentials): +# access_token = self._get_token_from_authentication_server( +# token, +# introspection_url, +# introspection_token, +# introspection_credentials +# ) +# +# if access_token and access_token.is_valid(scopes): +# request.client = access_token.application +# request.user = access_token.user or (access_token.application and access_token.application.user) +# request.scopes = scopes +# +# # this is needed by django rest framework +# request.access_token = access_token +# return True +# else: +# self._set_oauth2_error_on_request(request, access_token, scopes) +# return False diff --git a/apps/gooyal_oauth2/views/introspect.py b/apps/gooyal_oauth2/views/introspect.py index adf0bfb..dd60e4c 100644 --- a/apps/gooyal_oauth2/views/introspect.py +++ b/apps/gooyal_oauth2/views/introspect.py @@ -12,113 +12,113 @@ from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.views import ClientProtectedScopedResourceView from oauthlib.oauth2 import Server -from apps.gooyal_oauth2.validators import IntrospectOAuth2Validator +# from apps.gooyal_oauth2.validators import IntrospectOAuth2Validator -@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)) +# @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): - """ - Implementation of protected_resource decorator that saves the client on the - request for the view function to use. - - Cribbed from django-oauth-toolkit. - """ - _scopes = scopes or [] - - def decorator(view_func): - @functools.wraps(view_func) - def _validate(request, *args, **kwargs): - validator = IntrospectOAuth2Validator() - core = OAuthLibCore(Server(validator)) - valid, oauthlib_req = core.verify_request(request, scopes=_scopes) - if valid: - request.client = oauthlib_req.client - request.resource_owner = oauthlib_req.user - return view_func(request, *args, **kwargs) - return HttpResponseForbidden() - - return _validate - - return decorator - - -@require_http_methods(['GET', 'POST']) -@csrf_exempt -@protected_resource(scopes=['introspection']) -def introspect_token(request): - """ - Version of the introspection view protected by a regular scope instead of - read-write scopes. - - Also allows for the required scope to be changed using a setting. - """ - if request.method == 'GET': - token = request.GET.get("token", None) - else: - token = request.POST.get("token", None) - return IntrospectTokenView.get_token_response(token) +# def protected_resource(scopes=None): +# """ +# Implementation of protected_resource decorator that saves the client on the +# request for the view function to use. +# +# Cribbed from django-oauth-toolkit. +# """ +# _scopes = scopes or [] +# +# def decorator(view_func): +# @functools.wraps(view_func) +# def _validate(request, *args, **kwargs): +# validator = IntrospectOAuth2Validator() +# core = OAuthLibCore(Server(validator)) +# valid, oauthlib_req = core.verify_request(request, scopes=_scopes) +# if valid: +# request.client = oauthlib_req.client +# request.resource_owner = oauthlib_req.user +# return view_func(request, *args, **kwargs) +# return HttpResponseForbidden() +# +# return _validate +# +# return decorator +# +# +# @require_http_methods(['GET', 'POST']) +# @csrf_exempt +# @protected_resource(scopes=['introspection']) +# def introspect_token(request): +# """ +# Version of the introspection view protected by a regular scope instead of +# read-write scopes. +# +# Also allows for the required scope to be changed using a setting. +# """ +# if request.method == 'GET': +# token = request.GET.get("token", None) +# else: +# token = request.POST.get("token", None) +# return IntrospectTokenView.get_token_response(token) # @require_POST diff --git a/gooyal_accounts/urls.py b/gooyal_accounts/urls.py index 209c46c..73611e5 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.introspect 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, \ @@ -35,8 +35,8 @@ urlpatterns = [ path('logout/', LogoutView.as_view(), name='logout'), path('', home, name='home'), - path('oauth2/introspect', introspect_token), - path('oauth2/introspect/', introspect_token, name='introspect'), + # path('oauth2/introspect', introspect_token), + # path('oauth2/introspect/', introspect_token, name='introspect'), path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')), # url(r'^oauth2/register_scope/$', register_scope, name = 'register-scope'),