1
1
import json
2
2
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
4
8
from django .utils .decorators import method_decorator
5
9
from django .views .decorators .csrf import csrf_exempt
6
10
from django .views .generic import View
7
11
from oauthlib .oauth2 import DeviceApplicationServer
12
+ from oauthlib .oauth2 .rfc8628 .errors import (
13
+ AccessDenied ,
14
+ ExpiredTokenError ,
15
+ )
8
16
9
17
from 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
11
19
from oauth2_provider .settings import oauth2_settings
12
20
from oauth2_provider .views .mixins import OAuthLibMixin
13
21
@@ -31,3 +39,44 @@ def post(self, request, *args, **kwargs):
31
39
create_device (device_request , device_response )
32
40
33
41
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