diff --git a/apps/gooyal_oauth2/forms.py b/apps/gooyal_oauth2/forms.py index a7d83bd..001e1bf 100644 --- a/apps/gooyal_oauth2/forms.py +++ b/apps/gooyal_oauth2/forms.py @@ -5,6 +5,7 @@ Django forms for use with the gooyal-restrict-scopes package. from django import forms from oauth2_provider.scopes import get_scopes_backend +from .models import Application class DelimitedListField(forms.MultipleChoiceField): @@ -35,12 +36,12 @@ class ApplicationForm(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 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 + # 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=' ', @@ -49,3 +50,30 @@ class ApplicationForm(forms.ModelForm): class Meta: exclude = () + + +class ApplicationCreateForm(forms.ModelForm): + available_scopes_list = forms.CharField(widget=forms.Textarea(),required=False) + class Meta: + model = Application + fields = ['name', + 'client_id', + 'client_secret', + # 'available_scopes_list', + # 'authorization_grant_type', + 'redirect_uris', + 'post_logout_redirect_uris'] + + readonly_fields = ['client_id', 'client_secret'] + +class ApplicationUpdateForm(forms.ModelForm): + class Meta: + model = Application + fields = ['name', + 'client_id', + 'client_secret', + # 'authorization_grant_type', + 'redirect_uris', + 'post_logout_redirect_uris'] + + readonly_fields = ['client_id', 'client_secret'] diff --git a/apps/gooyal_oauth2/migrations/0002_alter_application_name.py b/apps/gooyal_oauth2/migrations/0002_alter_application_name.py new file mode 100644 index 0000000..35af33f --- /dev/null +++ b/apps/gooyal_oauth2/migrations/0002_alter_application_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-08-03 12:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gooyal_oauth2', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='application', + name='name', + field=models.CharField(blank=True, max_length=255, unique=True), + ), + ] diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py index b37263c..f149251 100644 --- a/apps/gooyal_oauth2/models.py +++ b/apps/gooyal_oauth2/models.py @@ -30,7 +30,7 @@ class Application(AbstractApplication): """ id=None uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) - + name = models.CharField(max_length=255, blank=False, unique=True) user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="%(app_label)s_%(class)s", @@ -54,6 +54,10 @@ class Application(AbstractApplication): app_scopes = set(self.allowed_scope.split()) return app_scopes.intersection(all_scopes) + # @property + # def available_scopes(self): + + def allows_grant_type(self, *grant_types): # Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE @@ -72,13 +76,13 @@ class Scope(models.Model): uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) application = models.ForeignKey( oauth2_settings.APPLICATION_MODEL, - models.CASCADE, + models.PROTECT, # 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' + related_name='available_scopes' ) resource = models.ForeignKey( Resource, @@ -132,11 +136,11 @@ class Scope(models.Model): """ endpoint = settings.RESOURCE_SERVER_REGISTER_SCOPE_URL if endpoint: - #  If the endpoint is set, make the callout to the authz server + # 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 + # 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 + # hook, so we want failures to halt the deployment response = requests.post( endpoint, json={ diff --git a/apps/gooyal_oauth2/urls.py b/apps/gooyal_oauth2/urls.py index aeb2755..1e647ff 100644 --- a/apps/gooyal_oauth2/urls.py +++ b/apps/gooyal_oauth2/urls.py @@ -1,11 +1,12 @@ -from django.urls import re_path +from django.urls import re_path, path from oauth2_provider import views from rest_framework import routers from .views.introspect import IntrospectTokenView, IntrospectApplicationView from .views import apis as api_views -app_name = "oauth2_provider" +from .views import pages +app_name = "gooyal_oauth2" base_urlpatterns = [ @@ -20,19 +21,19 @@ router = routers.SimpleRouter() router.register(r'apis/applications', api_views.ApplicationViewSet, basename='application') 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"), + # re_path(r"^applications/$", views.ApplicationList.as_view(), name="application_list"), + path("applications/", pages.ApplicationListView.as_view(), name="application_list"), + path('applications/register/', pages.ApplicationCreateView.as_view(), name='application_create'), + path("applications//", pages.ApplicationDetailView.as_view(), name="application_detail"), + # re_path(r"^applications/(?P[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"), + path("applications//update/", pages.ApplicationUpdateView.as_view(), name="application_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", - ), + # 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 = [ diff --git a/apps/gooyal_oauth2/views/pages.py b/apps/gooyal_oauth2/views/pages.py new file mode 100644 index 0000000..4ad080b --- /dev/null +++ b/apps/gooyal_oauth2/views/pages.py @@ -0,0 +1,61 @@ +from django.contrib.auth.decorators import login_required +from django.urls import resolve, reverse_lazy +from django.utils.decorators import method_decorator +from django.views.generic import CreateView, DetailView, ListView, UpdateView +from oauth2_provider.models import get_application_model + +from apps.gooyal_oauth2.forms import ApplicationCreateForm +from apps.gooyal_oauth2.models import Application + + +@method_decorator(login_required, name='dispatch') +class ApplicationCreateView(CreateView): + template_name = "gooyal_oauth2/application_create.html" + form_class = ApplicationCreateForm + + def get_success_url(self): + return reverse_lazy("gooyal_oauth2:application_detail", kwargs={"pk": self.object.pk}) + + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.client_type = Application.CLIENT_PUBLIC + # form.instance.client_type = Application.CLIENT_PUBLIC + return super().form_valid(form) + + +@method_decorator(login_required, name='dispatch') +class ApplicationUpdateView(UpdateView): + template_name = "gooyal_oauth2/application_create.html" + form_class = ApplicationCreateForm + + def get_queryset(self): + return Application.objects.filter(user=self.request.user) + + def get_success_url(self): + return reverse_lazy("gooyal_oauth2:application_detail", kwargs={"pk": self.object.pk}) + + def form_valid(self, form): + # form.instance.user = self.request.user + # form.instance.client_type = Application.CLIENT_PUBLIC + return super().form_valid(form) + + +@method_decorator(login_required, name='dispatch') +class ApplicationDetailView(DetailView): + model = Application + template_name = "gooyal_oauth2/application_create.html" + + + def get_queryset(self): + return Application.objects.filter(user=self.request.user) + + +@method_decorator(login_required, name='dispatch') +class ApplicationListView(ListView): + model = Application + template_name = "gooyal_oauth2/application_list.html" + paginate_by = 20 + + + def get_queryset(self): + return Application.objects.filter(user=self.request.user) diff --git a/templates/gooyal_oauth2/application_create.html b/templates/gooyal_oauth2/application_create.html new file mode 100644 index 0000000..1c09d51 --- /dev/null +++ b/templates/gooyal_oauth2/application_create.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load static %} +{% load crispy_forms_tags %} +{% load jalali_tags %} + +{% block title %}Application: create{% endblock %} +{% block page_title %}Application: create{% endblock %} + +{% block content %} +
+ {% csrf_token %} +
+ {{ form|crispy }} +
+
+
+
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/gooyal_oauth2/application_detail.html b/templates/gooyal_oauth2/application_detail.html new file mode 100644 index 0000000..57d6d15 --- /dev/null +++ b/templates/gooyal_oauth2/application_detail.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load static %} +{% load crispy_forms_tags %} +{% load jalali_tags %} + +{% block title %}Application: detail{% endblock %} +{% block page_title %}Application: detail{% endblock %} + +{% block content %} +

application: {{ object.name }}!

+

client id: {{ object.client_id }}!

+{% endblock %} diff --git a/templates/gooyal_oauth2/application_list.html b/templates/gooyal_oauth2/application_list.html new file mode 100644 index 0000000..9ff5cdb --- /dev/null +++ b/templates/gooyal_oauth2/application_list.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% load static %} +{% load jalali_tags %} +{% load crispy_forms_tags %} + +{% block title %}Application: list{% endblock %} +{% block page_title %}Application: list{% endblock %} +{% block page_action %} + {% include "include/filter_action.html" %} + + +{% endblock %} + + +{% block content %} + + + + + + + {% for obj in object_list %} + + + + + + {% endfor %} +
nameclient idactions
{{ obj.name }}{{ obj.client_id }} +
+
+ + +
+
+
+ {% include 'include/paginator.html' %} + +{% endblock %} \ No newline at end of file diff --git a/templates/include/filter_action.html b/templates/include/filter_action.html new file mode 100644 index 0000000..f71ec68 --- /dev/null +++ b/templates/include/filter_action.html @@ -0,0 +1,33 @@ +{% load crispy_forms_tags %} + + + + + diff --git a/templates/include/paginator.html b/templates/include/paginator.html new file mode 100644 index 0000000..f78b941 --- /dev/null +++ b/templates/include/paginator.html @@ -0,0 +1,35 @@ +{% load tags %} + + + + + {# #} diff --git a/templates/include/top_bar.html b/templates/include/top_bar.html index bb427d1..11a5992 100644 --- a/templates/include/top_bar.html +++ b/templates/include/top_bar.html @@ -16,7 +16,7 @@ {% endif %}