11import json
22
3- from django import http
3+ from django import forms , http
4+ from django .contrib .auth .decorators import login_required
5+ from django .shortcuts import render
6+ from django .urls import reverse
7+ from django .utils import timezone
48from django .utils .decorators import method_decorator
59from django .views .decorators .csrf import csrf_exempt
610from django .views .generic import View
711from oauthlib .oauth2 import DeviceApplicationServer
12+ from oauthlib .oauth2 .rfc8628 .errors import (
13+ AccessDenied ,
14+ ExpiredTokenError ,
15+ )
816
917from oauth2_provider .compat import login_not_required
10- from oauth2_provider .models import DeviceCodeResponse , DeviceRequest , create_device
18+ from oauth2_provider .models import Device , DeviceCodeResponse , DeviceRequest , create_device , get_device_model
1119from oauth2_provider .settings import oauth2_settings
1220from oauth2_provider .views .mixins import OAuthLibMixin
1321
@@ -31,3 +39,44 @@ def post(self, request, *args, **kwargs):
3139 create_device (device_request , device_response )
3240
3341 return http .JsonResponse (data = response , status = status , headers = headers )
42+
43+
44+ class DeviceForm (forms .Form ):
45+ user_code = forms .CharField (required = True )
46+
47+
48+ # it's common to see in real world products
49+ # device flow's only asking the user to sign in after they input the
50+ # user code but since the user has to be signed in regardless to approve the
51+ # device login we're making the decision here to require being logged in
52+ # up front
53+ @login_required
54+ def device_user_code_view (request ):
55+ form = DeviceForm (request .POST )
56+
57+ if request .method != "POST" :
58+ return render (request , "oauth2_provider/device/user_code.html" , {"form" : form })
59+
60+ if not form .is_valid ():
61+ return render (request , "oauth2_provider/device/user_code.html" , {"form" : form })
62+
63+ user_code : str = form .cleaned_data ["user_code" ]
64+ device : Device = get_device_model ().objects .get (user_code = user_code )
65+
66+ if device is None :
67+ form .add_error ("user_code" , "Incorrect user code" )
68+ return render (request , "oauth2_provider/device/user_code.html" , {"form" : form })
69+
70+ if timezone .now () > device .expires :
71+ device .status = device .EXPIRED
72+ device .save (update_fields = ["status" ])
73+ raise ExpiredTokenError
74+
75+ # User of device has already made their decision for this device
76+ if device .status in (device .DENIED , device .AUTHORIZED ):
77+ raise AccessDenied
78+
79+ # 308 to indicate we want to keep the redirect being a POST request
80+ return http .HttpResponsePermanentRedirect (
81+ reverse ("oauth2_provider:device-confirm" , kwargs = {"device_code" : device .device_code }), status = 308
82+ )
0 commit comments