From 3ee2f1936ad2cf8b2135cea991a7f2c1f8b4f15c Mon Sep 17 00:00:00 2001 From: mahdavi Date: Tue, 21 Jul 2020 13:06:39 +0430 Subject: [PATCH] bugfix in scope assignment --- apps/gooyal_dynamic_scopes/__init__.py | 1 - apps/gooyal_dynamic_scopes/admin.py | 12 -- apps/gooyal_dynamic_scopes/apps.py | 14 --- .../migrations/0001_initial.py | 29 ----- .../migrations/__init__.py | 0 apps/gooyal_dynamic_scopes/models.py | 79 -------------- apps/gooyal_dynamic_scopes/scopes.py | 25 ----- apps/gooyal_dynamic_scopes/signals.py | 26 ----- apps/gooyal_dynamic_scopes/views.py | 103 ------------------ apps/gooyal_oauth2/scopes.py | 14 ++- apps/gooyal_restrict_scopes/__init__.py | 0 apps/gooyal_restrict_scopes/admin.py | 25 ----- apps/gooyal_restrict_scopes/apps.py | 5 - apps/gooyal_restrict_scopes/forms.py | 51 --------- .../migrations/0001_initial.py | 44 -------- .../migrations/__init__.py | 0 apps/gooyal_restrict_scopes/models.py | 25 ----- apps/gooyal_restrict_scopes/scopes.py | 43 -------- gooyal_accounts/settings.py | 7 -- 19 files changed, 11 insertions(+), 492 deletions(-) delete mode 100644 apps/gooyal_dynamic_scopes/__init__.py delete mode 100644 apps/gooyal_dynamic_scopes/admin.py delete mode 100644 apps/gooyal_dynamic_scopes/apps.py delete mode 100644 apps/gooyal_dynamic_scopes/migrations/0001_initial.py delete mode 100644 apps/gooyal_dynamic_scopes/migrations/__init__.py delete mode 100644 apps/gooyal_dynamic_scopes/models.py delete mode 100644 apps/gooyal_dynamic_scopes/scopes.py delete mode 100644 apps/gooyal_dynamic_scopes/signals.py delete mode 100644 apps/gooyal_dynamic_scopes/views.py delete mode 100644 apps/gooyal_restrict_scopes/__init__.py delete mode 100755 apps/gooyal_restrict_scopes/admin.py delete mode 100755 apps/gooyal_restrict_scopes/apps.py delete mode 100644 apps/gooyal_restrict_scopes/forms.py delete mode 100755 apps/gooyal_restrict_scopes/migrations/0001_initial.py delete mode 100644 apps/gooyal_restrict_scopes/migrations/__init__.py delete mode 100644 apps/gooyal_restrict_scopes/models.py delete mode 100644 apps/gooyal_restrict_scopes/scopes.py diff --git a/apps/gooyal_dynamic_scopes/__init__.py b/apps/gooyal_dynamic_scopes/__init__.py deleted file mode 100644 index 2bc2894..0000000 --- a/apps/gooyal_dynamic_scopes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'apps.gooyal_dynamic_scopes.apps.AppConfig' diff --git a/apps/gooyal_dynamic_scopes/admin.py b/apps/gooyal_dynamic_scopes/admin.py deleted file mode 100644 index ec86326..0000000 --- a/apps/gooyal_dynamic_scopes/admin.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Django admin configuration for the gooyal-dynamic-scopes package. -""" - -from django.contrib import admin - -from .models import Scope - - -@admin.register(Scope) -class ScopeAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'is_default') diff --git a/apps/gooyal_dynamic_scopes/apps.py b/apps/gooyal_dynamic_scopes/apps.py deleted file mode 100644 index af07204..0000000 --- a/apps/gooyal_dynamic_scopes/apps.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Django app config for the gooyal-dynamic-scopes package. -""" - -from django.apps import AppConfig as BaseAppConfig -from django.db.models.signals import post_migrate - - -class AppConfig(BaseAppConfig): - name = 'apps.gooyal_dynamic_scopes' - - def ready(self): - from .signals import register_scopes - post_migrate.connect(register_scopes, sender=self) diff --git a/apps/gooyal_dynamic_scopes/migrations/0001_initial.py b/apps/gooyal_dynamic_scopes/migrations/0001_initial.py deleted file mode 100644 index 8c7b7d3..0000000 --- a/apps/gooyal_dynamic_scopes/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-09-04 13:38 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Scope', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='The name of the scope.', max_length=255, unique=True)), - ('description', models.TextField(help_text='A brief description of the scope. This text is displayed to users when authorising access for the scope.')), - ('is_default', models.BooleanField(default=False, help_text='Indicates if this scope should be included in the default scopes.')), - ('application', models.ForeignKey(blank=True, help_text='The application to which the scope belongs.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), - ], - ), - ] diff --git a/apps/gooyal_dynamic_scopes/migrations/__init__.py b/apps/gooyal_dynamic_scopes/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/gooyal_dynamic_scopes/models.py b/apps/gooyal_dynamic_scopes/models.py deleted file mode 100644 index 86758a3..0000000 --- a/apps/gooyal_dynamic_scopes/models.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Django models for the gooyal-dynamic-scopes package. -""" - -from django.db import models -from django.conf import settings - -import requests - -from oauth2_provider.settings import oauth2_settings - - -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 - # scope to be available to other applications in order to request access - #   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 - blank=True, null=True, - help_text='The application to which the scope belongs.' - ) - #: The name of the scope - name = models.CharField( - max_length=255, - unique=True, - help_text='The name of the scope.' - ) - #: A brief description of the scope - description = models.TextField( - help_text='A brief description of the scope. This text is displayed ' - 'to users when authorising access for the scope.' - ) - #: Indicates if the scope should be included in the default scopes - is_default = models.BooleanField( - default=False, - help_text='Indicates if this scope should be included in the default scopes.' - ) - - @classmethod - def register(cls, name, description, is_default=False): - """ - Registers a scope with the given values. It always creates an instance in - the local database, but if this resource server has an external authorisation - server, it will also register the scope there. - - Returns ``True`` on success. Should raise on failure. - """ - endpoint = settings.RESOURCE_SERVER_REGISTER_SCOPE_URL - if endpoint: - #  If the endpoint is set, make the callout to the authz server - token = "Bearer {}".format(oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN) - #  Let any failures bubble up - # The idea is to call this method during deployment as a post-migrate - #  hook, so we want failures to halt the deployment - response = requests.post( - endpoint, - json={ - 'name': name, - 'description': description, - 'is_default': is_default - }, - headers={"Authorization": token} - ) - #  Raise the exception for anything other than 20x responses - response.raise_for_status() - #  Always create/update the scope record locally - _ = Scope.objects.update_or_create( - name=name, - defaults={'description': description, 'is_default': is_default} - ) - return True diff --git a/apps/gooyal_dynamic_scopes/scopes.py b/apps/gooyal_dynamic_scopes/scopes.py deleted file mode 100644 index ae4a58b..0000000 --- a/apps/gooyal_dynamic_scopes/scopes.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Django OAuth Toolkit scopes backend for the gooyal-dynamic-scopes package. -""" - -from django.conf import settings -from django.utils import module_loading - -from oauth2_provider.scopes import BaseScopes - -from .models import Scope - - -class DynamicScopes(BaseScopes): - """ - Scopes backend that provides scopes from a Django model. - """ - - def get_all_scopes(self): - return {scope.name: scope.description for scope in Scope.objects.all()} - - def get_available_scopes(self, application=None, request=None, *args, **kwargs): - return list(self.get_all_scopes().keys()) - - def get_default_scopes(self, application=None, request=None, *args, **kwargs): - return [scope.name for scope in Scope.objects.filter(is_default=True)] diff --git a/apps/gooyal_dynamic_scopes/signals.py b/apps/gooyal_dynamic_scopes/signals.py deleted file mode 100644 index 3f2af94..0000000 --- a/apps/gooyal_dynamic_scopes/signals.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Django signal handlers for the gooyal-dynamic-scopes package. -""" - -from django.conf import settings - -from oauth2_provider.settings import oauth2_settings - -from .models import Scope - - -def register_scopes(app_config, verbosity=2, interactive=True, **kwargs): - """ - ``post_migrate`` signal handler that ensures the scopes required for the - introspection and register-scope endpoints are registered when running as an - authorisation server. - - The signal is connected in ``apps.py``. - """ - #  Register any scopes in the oauth2_provider settings - for name, description in oauth2_settings.SCOPES.items(): - Scope.register( - name, - description, - name in oauth2_settings.DEFAULT_SCOPES - ) diff --git a/apps/gooyal_dynamic_scopes/views.py b/apps/gooyal_dynamic_scopes/views.py deleted file mode 100644 index 2719c79..0000000 --- a/apps/gooyal_dynamic_scopes/views.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Django views for the gooyal-dynamic-scopes package. -""" - -import json -import functools - -from django.conf import settings -from django.http import HttpResponse, HttpResponseForbidden -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_http_methods, require_POST - -from oauthlib.oauth2 import Server - -from oauth2_provider.oauth2_backends import OAuthLibCore -from oauth2_provider.oauth2_validators import OAuth2Validator -from oauth2_provider.views import IntrospectTokenView - -from .models import Scope - - -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 = OAuth2Validator() - 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=[settings.INTROSPECT_SCOPE]) -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 -@csrf_exempt -@protected_resource(scopes=[settings.REGISTER_SCOPE_SCOPE]) -def register_scope(request): - """ - Implements an endpoint for registering a scope. - """ - #  Get the scope data from the request body - scope_data = json.loads(request.body) if request.body else {} - try: - try: - #  If a scope with the given name already exists, find it - scope = Scope.objects.get(name=scope_data['name']) - except Scope.DoesNotExist: - #  If no scope with the given name exists, create it - _ = Scope.objects.create( - application=request.client, - name=scope_data['name'], - description=scope_data['description'], - is_default=scope_data.get('is_default', False) - ) - #  Respond with a 201 Created - return HttpResponse(status=201) - except KeyError as exc: - #  A key missing in the data should be reported as a bad request - return HttpResponse( - status=400, - content="'{}' must be given in request data".format(exc.args[0]), - content_type='text/plain' - ) - #  If the scope does exist, check that the current application is the - #  owner of the scope before updating it - if scope.application and scope.application == request.client: - scope.description = scope_data['description'] - scope.is_default = scope_data.get('is_default', False) - scope.save() - return HttpResponse(status=200) - else: - return HttpResponse(status=403) diff --git a/apps/gooyal_oauth2/scopes.py b/apps/gooyal_oauth2/scopes.py index 62daa55..cf92a0f 100644 --- a/apps/gooyal_oauth2/scopes.py +++ b/apps/gooyal_oauth2/scopes.py @@ -15,11 +15,19 @@ class Scopes(BaseScopes): Scopes backend that provides scopes from a Django model. """ + def get_queryset(self, application=None): + queryset = Scope.objects.all() + if application: + queryset = queryset.filter(name__in=application.allowed_scopes) + return queryset + + def get_all_scopes(self): - return {scope.name: scope.description for scope in Scope.objects.all()} + return {scope.name: scope.description for scope in self.get_queryset().all()} def get_available_scopes(self, application=None, request=None, *args, **kwargs): - return list(self.get_all_scopes().keys()) + scopes = [scope.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 Scope.objects.filter(is_default=True)] + return [scope.name for scope in self.get_queryset(application).filter(is_default=True).all()] diff --git a/apps/gooyal_restrict_scopes/__init__.py b/apps/gooyal_restrict_scopes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/gooyal_restrict_scopes/admin.py b/apps/gooyal_restrict_scopes/admin.py deleted file mode 100755 index 068721b..0000000 --- a/apps/gooyal_restrict_scopes/admin.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Django admin configuration for the gooyal-restrict-scopes package. -""" - -from django.contrib import admin -from django.contrib.admin.sites import NotRegistered - -from oauth2_provider.admin import ApplicationAdmin - -from .models import RestrictedApplication -from .forms import RestrictedApplicationForm - - -# The restricted application is registered by Django OAuth Toolkit, but we want -# to provide our own admin that uses our form -try: - admin.site.unregister(RestrictedApplication) -except NotRegistered: - pass - -@admin.register(RestrictedApplication) -class RestrictedApplicationAdmin(ApplicationAdmin): - form = RestrictedApplicationForm - -# admin.site.register(RestrictedApplication, RestrictedApplicationAdmin) diff --git a/apps/gooyal_restrict_scopes/apps.py b/apps/gooyal_restrict_scopes/apps.py deleted file mode 100755 index 70b337a..0000000 --- a/apps/gooyal_restrict_scopes/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class GooyalRestrictScopesConfig(AppConfig): - name = 'apps.gooyal_restrict_scopes' diff --git a/apps/gooyal_restrict_scopes/forms.py b/apps/gooyal_restrict_scopes/forms.py deleted file mode 100644 index ff91147..0000000 --- a/apps/gooyal_restrict_scopes/forms.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Django forms for use with the gooyal-restrict-scopes package. -""" - -from django import forms - -from oauth2_provider.scopes import get_scopes_backend - - -class DelimitedListField(forms.MultipleChoiceField): - """ - Django form field that allows for the use of list widgets with a text field - containing a delimited list. - """ - delimiter = ',' - - def __init__(self, delimiter = None, *args, **kwargs): - super().__init__(*args, **kwargs) - self.delimiter = delimiter or self.delimiter - - def prepare_value(self, value): - # If the value is already a list or tuple, just use it as-is - if isinstance(value, (list, tuple)): return value - # Otherwise, prepare the value by splitting on the delimiter, trimming - # leading and trailing whitespace and excluding empty values - return [p.strip() for p in value.split(self.delimiter) if p.strip()] - - def clean(self, value): - # Let the parent clean the value first, then join the result using the - # specified delimiter - return self.delimiter.join(super().clean(value)) - - -class RestrictedApplicationForm(forms.ModelForm): - """ - Form for creating or updating a restricted application. - """ - # allowed_scope is a space-delimited list, but we want to present - # a selection of valid scopes with checkboxes - allowed_scope = DelimitedListField( - label = 'Allowed scopes', - # The choices and initial values are callables, because the scopes might - # not be available at import type, e.g. if coming from the database - choices = lambda: get_scopes_backend().get_all_scopes().items(), - initial = lambda: get_scopes_backend().get_default_scopes(), - delimiter = ' ', - widget = forms.CheckboxSelectMultiple - ) - - class Meta: - exclude = () diff --git a/apps/gooyal_restrict_scopes/migrations/0001_initial.py b/apps/gooyal_restrict_scopes/migrations/0001_initial.py deleted file mode 100755 index 0698919..0000000 --- a/apps/gooyal_restrict_scopes/migrations/0001_initial.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-09-01 15:44 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import oauth2_provider.generators -import oauth2_provider.validators - - -class Migration(migrations.Migration): - - initial = True - run_before = [ - ('oauth2_provider', '0001_initial'), - ] - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='RestrictedApplication', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)), - ('redirect_uris', models.TextField(blank=True, help_text='Allowed URIs list, space separated')), - ('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], max_length=32)), - ('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials')], max_length=32)), - ('client_secret', models.CharField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=255)), - ('name', models.CharField(blank=True, max_length=255)), - ('skip_authorization', models.BooleanField(default=False)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('allowed_scope', models.TextField(blank=True)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='gooyal_restrict_scopes_restrictedapplication', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/apps/gooyal_restrict_scopes/migrations/__init__.py b/apps/gooyal_restrict_scopes/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/gooyal_restrict_scopes/models.py b/apps/gooyal_restrict_scopes/models.py deleted file mode 100644 index b47cc20..0000000 --- a/apps/gooyal_restrict_scopes/models.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Django models for the gooyal-restrict-scopes package. -""" - -from django.db import models - -from oauth2_provider.models import AbstractApplication -from oauth2_provider.scopes import get_scopes_backend - - -class RestrictedApplication(AbstractApplication): - """ - Application model for use with Django OAuth Toolkit that allows the scopes - available to an application to be restricted on a per-application basis. - """ - allowed_scope = models.TextField(blank = True) - - @property - def allowed_scopes(self): - """ - Returns the set of allowed scope names for this application. - """ - all_scopes = set(get_scopes_backend().get_all_scopes().keys()) - app_scopes = set(self.allowed_scope.split()) - return app_scopes.intersection(all_scopes) diff --git a/apps/gooyal_restrict_scopes/scopes.py b/apps/gooyal_restrict_scopes/scopes.py deleted file mode 100644 index 1c1ac62..0000000 --- a/apps/gooyal_restrict_scopes/scopes.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Django OAuth Toolkit scopes backend for the gooyal-restrict-scopes package. -""" - -from django.conf import settings -from django.utils import module_loading - -from oauth2_provider.scopes import BaseScopes - - -class RestrictApplicationScopes(BaseScopes): - """ - Scopes backend that wraps another backend and restricts the scopes available - to an application based on the application's ``allowed_scopes``. - """ - def __init__(self): - # Initialise the wrapped backend from settings - self._wrapped = module_loading.import_string( - getattr(settings, 'GOOYAL_RESTRICT_SCOPES', {}).get( - 'WRAPPED_SCOPES_BACKEND_CLASS', - 'oauth2_provider.scopes.SettingsScopes' - ) - )() - - def get_all_scopes(self): - # Just return all the available scopes from the wrapped backend - return self._wrapped.get_all_scopes() - - def get_available_scopes(self, application = None, request = None, *args, **kwargs): - # Get the available scopes from the wrapped backend, then filter them - # based on the allowed_scopes of the application - scopes = self._wrapped.get_available_scopes(application, request, *args, **kwargs) - if application: - scopes = [s for s in scopes if s in application.allowed_scopes] - return scopes - - def get_default_scopes(self, application = None, request = None, *args, **kwargs): - # Get the default scopes from the wrapped backend, then filter them - # based on the allowed_scopes of the application - scopes = self._wrapped.get_default_scopes(application, request, *args, **kwargs) - if application: - scopes = [s for s in scopes if s in application.allowed_scopes] - return scopes diff --git a/gooyal_accounts/settings.py b/gooyal_accounts/settings.py index 58ebdb6..d1c9c1b 100644 --- a/gooyal_accounts/settings.py +++ b/gooyal_accounts/settings.py @@ -42,8 +42,6 @@ INSTALLED_APPS = [ 'corsheaders', 'apps.users', 'apps.transactions', - # 'apps.gooyal_restrict_scopes', - # 'apps.gooyal_dynamic_scopes', 'apps.gooyal_oauth2', ] @@ -74,11 +72,6 @@ OAUTH2_PROVIDER = { 'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.MultiGatewayOAuth2Validator' } -# GOOYAL_RESTRICT_SCOPES = { -# 'WRAPPED_SCOPES_BACKEND_CLASS': 'oauth2_provider.scopes.SettingsScopes', -# } - - # GOOYAL_DYNAMIC_SCOPES RESOURCE_SERVER_REGISTER_SCOPE_URL = None INTROSPECT_SCOPE = None