6
6
import com .nimbusds .oauth2 .sdk .http .JakartaServletUtils ;
7
7
import jakarta .servlet .http .HttpServletRequest ;
8
8
import jakarta .servlet .http .HttpSession ;
9
+ import oidc .crypto .SimpleEncryptionHandler ;
9
10
import oidc .exceptions .InvalidGrantException ;
10
11
import oidc .exceptions .UnknownClientException ;
11
12
import oidc .model .DeviceAuthorization ;
35
36
36
37
import java .io .IOException ;
37
38
import java .net .URLEncoder ;
39
+ import java .nio .charset .Charset ;
38
40
import java .security .SecureRandom ;
39
41
import java .time .Instant ;
40
42
import java .time .temporal .ChronoUnit ;
41
43
import java .util .*;
42
44
import java .util .concurrent .atomic .AtomicBoolean ;
45
+ import java .util .concurrent .atomic .AtomicReference ;
43
46
44
47
import static com .nimbusds .oauth2 .sdk .GrantType .DEVICE_CODE ;
45
48
import static java .nio .charset .Charset .defaultCharset ;
@@ -106,12 +109,15 @@ public ResponseEntity<Map<String, Object>> deviceAuthorization(HttpServletReques
106
109
);
107
110
deviceAuthorizationRepository .save (deviceAuthorization );
108
111
112
+ String hint = URLEncoder .encode (SimpleEncryptionHandler .encrypt (userCode ), Charset .defaultCharset ());
113
+ String verificationUrlWithHint = String .format ("%s?hint=%s" , verificationUrl , hint );
114
+
109
115
Map <String , Object > results = Map .of (
110
116
"device_code" , deviceCode ,
111
117
"user_code" , userCode ,
112
- "verification_uri" , verificationUrl ,
118
+ "verification_uri" , verificationUrlWithHint ,
113
119
"verification_uri_complete" , String .format ("%s?user_code=%s" , verificationUrl , userCode ),
114
- "qr_code" , QRGenerator .qrCode (verificationUrl ).getImage (),
120
+ "qr_code" , QRGenerator .qrCode (verificationUrlWithHint ).getImage (),
115
121
//The lifetime in seconds of the "device_code" and "user_code"
116
122
"expires_in" , 60 * 15 ,
117
123
//The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint
@@ -122,18 +128,23 @@ public ResponseEntity<Map<String, Object>> deviceAuthorization(HttpServletReques
122
128
123
129
@ GetMapping (value = "oidc/verify" )
124
130
public ModelAndView verification (@ RequestParam (value = "user_code" , required = false ) String userCode ,
131
+ @ RequestParam (value = "hint" , required = false ) String hint ,
125
132
@ RequestParam (value = "error" , required = false , defaultValue = "false" ) String error ,
126
133
HttpServletRequest request ) {
134
+ AtomicReference <String > userCodeRef = new AtomicReference <>(userCode );
127
135
Map <String , Object > model = new HashMap <>();
128
- if (StringUtils .hasText (userCode )) {
136
+ if (StringUtils .hasText (hint )) {
137
+ userCodeRef .set (SimpleEncryptionHandler .decrypt (hint ));
138
+ }
139
+ if (StringUtils .hasText (userCodeRef .get ())) {
129
140
//When the code checks out, then retrieve the client for displaying purposes
130
- findByUserCode (userCode )
141
+ findByUserCode (userCodeRef . get () )
131
142
.flatMap (deviceAuthorization -> openIDClientRepository .findOptionalByClientId (deviceAuthorization .getClientId ()))
132
143
//Check the very strange use-case for the client not existing anymore
133
144
.ifPresent (openIDClient -> {
134
145
model .put ("client" , openIDClient );
135
- model .put ("userCode" , userCode );
136
- model .put ("completeURI" , true );
146
+ model .put ("userCode" , StringUtils . hasText ( userCode ) ? userCodeRef . get () : null );
147
+ model .put ("completeURI" , StringUtils . hasText ( userCode ) );
137
148
});
138
149
}
139
150
model .putIfAbsent ("completeURI" , false );
@@ -169,7 +180,7 @@ public ModelAndView postVerify(@RequestParam Map<String, String> body, HttpServl
169
180
logout (request );
170
181
return new ModelAndView (new RedirectView (deviceAuthorizeURL (deviceAuthorization ), true ));
171
182
})
172
- .orElseGet (() -> this .verification (null , "true" , request ));
183
+ .orElseGet (() -> this .verification (null , null , "true" , request ));
173
184
return modelAndView ;
174
185
}
175
186
@@ -178,7 +189,7 @@ public ModelAndView deviceAuthorize(@RequestParam(value = "state") String state,
178
189
@ RequestParam (value = "user_code" ) String userCode ,
179
190
Authentication authentication ) {
180
191
LOG .debug (String .format ("/oidc/device_authorize %s %s" , authentication .getDetails (), userCode ));
181
- //If the state (e.g. userCode) corresponds with a DeviceAuthentication then mark this as success and inform the user
192
+ //If the state (e.g. userCode) corresponds with a DeviceAuthentication then mark this as succes and inform the user
182
193
Map <String , Object > model = new HashMap <>();
183
194
Optional <DeviceAuthorization > optionalDeviceAuthorization = findByUserCode (userCode );
184
195
AtomicBoolean stateMatches = new AtomicBoolean (false );
0 commit comments