From 93e7da8f67e3b13102aafa612e30db3c9c6aa464 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 31 May 2022 22:49:39 -0400 Subject: [PATCH] Add Application ViewSet for client-credentials grant type --- .gitignore | 2 ++ backend/accounts/serializers.py | 25 +++++++++++++++++++++++++ backend/accounts/urls.py | 2 ++ backend/accounts/views.py | 19 +++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/.gitignore b/.gitignore index e2149ba7..9bbb8e60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.env + # IDE files .vscode/ .idea/ diff --git a/backend/accounts/serializers.py b/backend/accounts/serializers.py index 73e27117..9255666d 100644 --- a/backend/accounts/serializers.py +++ b/backend/accounts/serializers.py @@ -1,5 +1,6 @@ from django.utils import timezone from django.utils.crypto import get_random_string +from oauth2_provider.models import Application from rest_framework import serializers from accounts.mixins import ManyToManySaveMixin @@ -7,6 +8,30 @@ from accounts.verification import sendEmailVerification, sendSMSVerification +class ApplicationSerializer(serializers.ModelSerializer): + # Hardcode grant type (for now) so it's impossible for someone to create a copycat + # Penn Labs login flow to collect access tokens on behalf of unsuspecting users. + # We may want to relax this if we add more a explicit permission grant prompt + # to the authentication flow for non-Labs applications. For now we are just + # allowing application registration on behalf of a user's own resources, + # via the client credentials grant: + # https://oauth.net/2/grant-types/client-credentials/ + authorization_grant_type = serializers.CharField( + default=Application.GRANT_CLIENT_CREDENTIALS + ) + client_type = serializers.CharField(default=Application.CLIENT_CONFIDENTIAL) + + class Meta: + model = Application + read_only = [ + "client_id", + "client_secret", + "authorization_grant_type", + "client_type", + ] + fields = read_only + ["name", "redirect_uris"] + + class SchoolSerializer(serializers.ModelSerializer): class Meta: model = School diff --git a/backend/accounts/urls.py b/backend/accounts/urls.py index d6b89519..c59d5dcf 100644 --- a/backend/accounts/urls.py +++ b/backend/accounts/urls.py @@ -4,6 +4,7 @@ from rest_framework import routers from accounts.views import ( + ApplicationViewSet, DevLoginView, DevLogoutView, EmailViewSet, @@ -22,6 +23,7 @@ app_name = "accounts" router = routers.SimpleRouter() +router.register("application", ApplicationViewSet, basename="application") router.register("me/phonenumber", PhoneNumberViewSet, basename="me-phonenumber") router.register("me/email", EmailViewSet, basename="me-email") router.register("majors", MajorViewSet, basename="majors") diff --git a/backend/accounts/views.py b/backend/accounts/views.py index b2c17847..020458f2 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -29,6 +29,7 @@ from accounts.models import Major, School, User from accounts.serializers import ( + ApplicationSerializer, EmailSerializer, MajorSerializer, PhoneNumberSerializer, @@ -123,6 +124,24 @@ def get(self, request): return redirect("accounts:login") +class ApplicationViewSet(viewsets.ModelViewSet): + """ + A ViewSet for managing Applications, through which users can register + confidential client credentials to access API resources on their behalf + (e.g. for external developers writing automated scripts that access authenticated + Penn Labs routes, logged into their own Penn Labs account). + See ApplicationSerializer for an explanation of why we are not currently allowing + applications of other grant types (other than `client-credentials`). + """ + + serializer_class = ApplicationSerializer + lookup_field = "client_id" + permission_classes = [IsAuthenticated] + + def get_queryset(self): + return self.request.user.oauth2_provider_application.all() + + @method_decorator(csrf_exempt, name="dispatch") class UUIDIntrospectTokenView(IntrospectTokenView): @staticmethod