Skip to content

Commit 13290f0

Browse files
committed
Update token endpoint
It needs handled differently depending on the device grant type or not it also needs to be rate limited to adhrere to the polling section in the spec so a device can't spam the token endpoint
1 parent 8a46c6b commit 13290f0

File tree

1 file changed

+44
-1
lines changed

1 file changed

+44
-1
lines changed

oauth2_provider/views/base.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
from urllib.parse import parse_qsl, urlencode, urlparse
55

6+
from django import http
67
from django.contrib.auth.mixins import LoginRequiredMixin
78
from django.contrib.auth.views import redirect_to_login
89
from django.http import HttpResponse
@@ -12,6 +13,13 @@
1213
from django.views.decorators.csrf import csrf_exempt
1314
from django.views.decorators.debug import sensitive_post_parameters
1415
from django.views.generic import FormView, View
16+
from django_ratelimit.decorators import ratelimit
17+
from oauthlib.oauth2.rfc8628.errors import (
18+
AccessDenied,
19+
AuthorizationPendingError,
20+
)
21+
22+
from oauth2_provider.models import Device
1523

1624
from ..compat import login_not_required
1725
from ..exceptions import OAuthToolkitError
@@ -290,10 +298,13 @@ class TokenView(OAuthLibMixin, View):
290298
* Authorization code
291299
* Password
292300
* Client credentials
301+
* Device code flow (specifically for the device polling stage)
293302
"""
294303

295304
@method_decorator(sensitive_post_parameters("password", "client_secret"))
296-
def post(self, request, *args, **kwargs):
305+
def authorization_flow_token_response(
306+
self, request: http.HttpRequest, *args, **kwargs
307+
) -> http.HttpResponse:
297308
url, headers, body, status = self.create_token_response(request)
298309
if status == 200:
299310
access_token = json.loads(body).get("access_token")
@@ -307,6 +318,38 @@ def post(self, request, *args, **kwargs):
307318
response[k] = v
308319
return response
309320

321+
@method_decorator(ratelimit(key="ip", rate=f"1/{oauth2_settings.DEVICE_FLOW_INTERVAL}"))
322+
def device_flow_token_response(
323+
self, request: http.HttpRequest, device_code: str, *args, **kwargs
324+
) -> http.HttpResponse:
325+
device = Device.objects.get(device_code=device_code)
326+
327+
if device.status == device.AUTHORIZATION_PENDING:
328+
raise AuthorizationPendingError
329+
330+
if device.status == device.DENIED:
331+
raise AccessDenied
332+
333+
url, headers, body, status = self.create_token_response(request)
334+
335+
if status != 200:
336+
return http.JsonResponse(data=json.loads(body), status=status)
337+
338+
response = http.JsonResponse(data=json.loads(body), status=status)
339+
340+
for k, v in headers.items():
341+
response[k] = v
342+
343+
device.status = device.EXPIRED
344+
device.save(update_fields=["status"])
345+
return response
346+
347+
def post(self, request: http.HttpRequest, *args, **kwargs) -> http.HttpResponse:
348+
params = request.POST
349+
if params.get("grant_type") == "urn:ietf:params:oauth:grant-type:device_code":
350+
return self.device_flow_token_response(request, params["device_code"])
351+
return self.authorization_flow_token_response(request)
352+
310353

311354
@method_decorator(csrf_exempt, name="dispatch")
312355
@method_decorator(login_not_required, name="dispatch")

0 commit comments

Comments
 (0)