Skip to content

Commit 8b76b0d

Browse files
committed
Add an authorize endpoint that uses JSON instead of a Django template/HTML form
1 parent a7f6468 commit 8b76b0d

File tree

3 files changed

+104
-78
lines changed

3 files changed

+104
-78
lines changed

oauth2_provider/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
base_urlpatterns = [
1010
re_path(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"),
11+
re_path(r"authorize.json/$", views.AuthorizationJSONView.as_view(),
12+
name="authorize-json"),
1113
re_path(r"^token/$", views.TokenView.as_view(), name="token"),
1214
re_path(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"),
1315
re_path(r"^introspect/$", views.IntrospectTokenView.as_view(), name="introspect"),

oauth2_provider/views/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# flake8: noqa
2-
from .base import AuthorizationView, TokenView, RevokeTokenView # isort:skip
2+
from .base import AuthorizationView, AuthorizationJSONView, TokenView, RevokeTokenView # isort:skip
33
from .application import (
44
ApplicationDelete,
55
ApplicationDetail,

oauth2_provider/views/base.py

Lines changed: 101 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
from django.views.decorators.debug import sensitive_post_parameters
1313
from django.views.generic import FormView, View
1414

15+
# JB
16+
from django.forms import model_to_dict
17+
from django.core.serializers.json import DjangoJSONEncoder
18+
from django.db.models import Model
19+
1520
from ..exceptions import OAuthToolkitError
1621
from ..forms import AllowForm
1722
from ..http import OAuth2ResponseRedirect
@@ -65,8 +70,100 @@ def redirect(self, redirect_to, application):
6570

6671
RFC3339 = "%Y-%m-%dT%H:%M:%SZ"
6772

73+
class AuthorizationMixin:
74+
def get_context(self, request, *args, **kwargs):
75+
try:
76+
scopes, credentials = self.validate_authorization_request(request)
77+
except OAuthToolkitError as error:
78+
# Application is not available at this time.
79+
return self.error_response(error, application=None)
80+
81+
prompt = request.GET.get("prompt")
82+
if prompt == "login":
83+
return self.handle_prompt_login()
84+
85+
all_scopes = get_scopes_backend().get_all_scopes()
86+
kwargs["scopes_descriptions"] = [all_scopes[scope] for scope in scopes]
87+
kwargs["scopes"] = scopes
88+
# at this point we know an Application instance with such client_id exists in the database
89+
90+
# TODO: Cache this!
91+
application = get_application_model().objects.get(client_id=credentials["client_id"])
92+
93+
kwargs["application"] = application
94+
kwargs["client_id"] = credentials["client_id"]
95+
kwargs["redirect_uri"] = credentials["redirect_uri"]
96+
kwargs["response_type"] = credentials["response_type"]
97+
kwargs["state"] = credentials["state"]
98+
if "code_challenge" in credentials:
99+
kwargs["code_challenge"] = credentials["code_challenge"]
100+
if "code_challenge_method" in credentials:
101+
kwargs["code_challenge_method"] = credentials["code_challenge_method"]
102+
if "nonce" in credentials:
103+
kwargs["nonce"] = credentials["nonce"]
104+
if "claims" in credentials:
105+
kwargs["claims"] = json.dumps(credentials["claims"])
106+
107+
self.oauth2_data = kwargs
108+
109+
# Check to see if the user has already granted access and return
110+
# a successful response depending on "approval_prompt" url parameter
111+
require_approval = request.GET.get("approval_prompt", oauth2_settings.REQUEST_APPROVAL_PROMPT)
112+
113+
try:
114+
# If skip_authorization field is True, skip the authorization screen even
115+
# if this is the first use of the application and there was no previous authorization.
116+
# This is useful for in-house applications-> assume an in-house applications
117+
# are already approved.
118+
if application.skip_authorization:
119+
uri, headers, body, status = self.create_authorization_response(
120+
request=self.request, scopes=" ".join(scopes), credentials=credentials, allow=True
121+
)
122+
return self.redirect(uri, application)
123+
124+
elif require_approval == "auto":
125+
tokens = (
126+
get_access_token_model()
127+
.objects.filter(
128+
user=request.user, application=kwargs["application"], expires__gt=timezone.now()
129+
)
130+
.all()
131+
)
132+
133+
# check past authorizations regarded the same scopes as the current one
134+
for token in tokens:
135+
if token.allow_scopes(scopes):
136+
uri, headers, body, status = self.create_authorization_response(
137+
request=self.request,
138+
scopes=" ".join(scopes),
139+
credentials=credentials,
140+
allow=True,
141+
)
142+
return self.redirect(uri, application)
143+
144+
except OAuthToolkitError as error:
145+
return self.error_response(error, application)
146+
return kwargs
147+
148+
class AuthorizationJSONView(BaseAuthorizationView, AuthorizationMixin):
149+
def get(self, request, *args, **kwargs):
150+
context = self.get_context(request, *args, **kwargs)
151+
return HttpResponse(
152+
content=json.dumps(context, cls=self.ExtendedEncoder),
153+
status=200)
68154

69-
class AuthorizationView(BaseAuthorizationView, FormView):
155+
def post(self, request, *args, **kwargs):
156+
# handle JSON post, sanitization etc.
157+
pass
158+
159+
class ExtendedEncoder(DjangoJSONEncoder):
160+
def default(self, o):
161+
if isinstance(o, Model):
162+
return model_to_dict(o)
163+
else:
164+
return super().default(o)
165+
166+
class AuthorizationView(BaseAuthorizationView, FormView, AuthorizationMixin):
70167
"""
71168
Implements an endpoint to handle *Authorization Requests* as in :rfc:`4.1.1` and prompting the
72169
user with a form to determine if she authorizes the client application to access her data.
@@ -128,7 +225,6 @@ def form_valid(self, form):
128225

129226
scopes = form.cleaned_data.get("scope")
130227
allow = form.cleaned_data.get("allow")
131-
132228
try:
133229
uri, headers, body, status = self.create_authorization_response(
134230
request=self.request, scopes=scopes, credentials=credentials, allow=allow
@@ -141,82 +237,10 @@ def form_valid(self, form):
141237
return self.redirect(self.success_url, application)
142238

143239
def get(self, request, *args, **kwargs):
144-
try:
145-
scopes, credentials = self.validate_authorization_request(request)
146-
except OAuthToolkitError as error:
147-
# Application is not available at this time.
148-
return self.error_response(error, application=None)
149-
150-
prompt = request.GET.get("prompt")
151-
if prompt == "login":
152-
return self.handle_prompt_login()
153-
154-
all_scopes = get_scopes_backend().get_all_scopes()
155-
kwargs["scopes_descriptions"] = [all_scopes[scope] for scope in scopes]
156-
kwargs["scopes"] = scopes
157-
# at this point we know an Application instance with such client_id exists in the database
158-
159-
# TODO: Cache this!
160-
application = get_application_model().objects.get(client_id=credentials["client_id"])
161-
162-
kwargs["application"] = application
163-
kwargs["client_id"] = credentials["client_id"]
164-
kwargs["redirect_uri"] = credentials["redirect_uri"]
165-
kwargs["response_type"] = credentials["response_type"]
166-
kwargs["state"] = credentials["state"]
167-
if "code_challenge" in credentials:
168-
kwargs["code_challenge"] = credentials["code_challenge"]
169-
if "code_challenge_method" in credentials:
170-
kwargs["code_challenge_method"] = credentials["code_challenge_method"]
171-
if "nonce" in credentials:
172-
kwargs["nonce"] = credentials["nonce"]
173-
if "claims" in credentials:
174-
kwargs["claims"] = json.dumps(credentials["claims"])
175-
176-
self.oauth2_data = kwargs
177-
# following two loc are here only because of https://code.djangoproject.com/ticket/17795
240+
context = self.get_context(request, *args, **kwargs)
178241
form = self.get_form(self.get_form_class())
179-
kwargs["form"] = form
180-
181-
# Check to see if the user has already granted access and return
182-
# a successful response depending on "approval_prompt" url parameter
183-
require_approval = request.GET.get("approval_prompt", oauth2_settings.REQUEST_APPROVAL_PROMPT)
184-
185-
try:
186-
# If skip_authorization field is True, skip the authorization screen even
187-
# if this is the first use of the application and there was no previous authorization.
188-
# This is useful for in-house applications-> assume an in-house applications
189-
# are already approved.
190-
if application.skip_authorization:
191-
uri, headers, body, status = self.create_authorization_response(
192-
request=self.request, scopes=" ".join(scopes), credentials=credentials, allow=True
193-
)
194-
return self.redirect(uri, application)
195-
196-
elif require_approval == "auto":
197-
tokens = (
198-
get_access_token_model()
199-
.objects.filter(
200-
user=request.user, application=kwargs["application"], expires__gt=timezone.now()
201-
)
202-
.all()
203-
)
204-
205-
# check past authorizations regarded the same scopes as the current one
206-
for token in tokens:
207-
if token.allow_scopes(scopes):
208-
uri, headers, body, status = self.create_authorization_response(
209-
request=self.request,
210-
scopes=" ".join(scopes),
211-
credentials=credentials,
212-
allow=True,
213-
)
214-
return self.redirect(uri, application)
215-
216-
except OAuthToolkitError as error:
217-
return self.error_response(error, application)
218-
219-
return self.render_to_response(self.get_context_data(**kwargs))
242+
context["form"] = form
243+
return self.render_to_response(self.get_context_data(**context))
220244

221245
def handle_prompt_login(self):
222246
path = self.request.build_absolute_uri()

0 commit comments

Comments
 (0)