3
3
import logging
4
4
from urllib .parse import parse_qsl , urlencode , urlparse
5
5
6
+ from django import http
6
7
from django .contrib .auth .mixins import LoginRequiredMixin
7
8
from django .contrib .auth .views import redirect_to_login
8
9
from django .http import HttpResponse
12
13
from django .views .decorators .csrf import csrf_exempt
13
14
from django .views .decorators .debug import sensitive_post_parameters
14
15
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
15
23
16
24
from ..compat import login_not_required
17
25
from ..exceptions import OAuthToolkitError
@@ -290,10 +298,13 @@ class TokenView(OAuthLibMixin, View):
290
298
* Authorization code
291
299
* Password
292
300
* Client credentials
301
+ * Device code flow (specifically for the device polling stage)
293
302
"""
294
303
295
304
@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 :
297
308
url , headers , body , status = self .create_token_response (request )
298
309
if status == 200 :
299
310
access_token = json .loads (body ).get ("access_token" )
@@ -307,6 +318,38 @@ def post(self, request, *args, **kwargs):
307
318
response [k ] = v
308
319
return response
309
320
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
+
310
353
311
354
@method_decorator (csrf_exempt , name = "dispatch" )
312
355
@method_decorator (login_not_required , name = "dispatch" )
0 commit comments