001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.device; 019 020 021import com.nimbusds.common.contenttype.ContentType; 022import com.nimbusds.oauth2.sdk.ParseException; 023import com.nimbusds.oauth2.sdk.SuccessResponse; 024import com.nimbusds.oauth2.sdk.http.HTTPResponse; 025import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 026import net.jcip.annotations.Immutable; 027import net.minidev.json.JSONObject; 028 029import java.net.URI; 030import java.util.*; 031 032 033/** 034 * A device authorization response from the device authorization endpoint. 035 * 036 * <p> 037 * Example HTTP response: 038 * 039 * <pre> 040 * HTTP/1.1 200 OK 041 * Content-Type: application/json;charset=UTF-8 042 * Cache-Control: no-store 043 * Pragma: no-cache 044 * 045 * { 046 * "device_code" : "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", 047 * "user_code" : "WDJB-MJHT", 048 * "verification_uri" : "https://example.com/device", 049 * "verification_uri_complete" : "https://example.com/device?user_code=WDJB-MJHT", 050 * "expires_in" : 1800, 051 * "interval" : 5 052 * } 053 * </pre> 054 * 055 * <p>Related specifications: 056 * 057 * <ul> 058 * <li>OAuth 2.0 Device Authorization Grant (RFC 8628) 059 * </ul> 060 */ 061@Immutable 062public class DeviceAuthorizationSuccessResponse extends DeviceAuthorizationResponse implements SuccessResponse { 063 064 065 /** 066 * The registered parameter names. 067 */ 068 private static final Set<String> REGISTERED_PARAMETER_NAMES; 069 070 static { 071 Set<String> p = new HashSet<>(); 072 073 p.add("device_code"); 074 p.add("user_code"); 075 p.add("verification_uri"); 076 p.add("verification_uri_complete"); 077 p.add("expires_in"); 078 p.add("interval"); 079 080 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 081 } 082 083 084 /** 085 * The device verification code. 086 */ 087 private final DeviceCode deviceCode; 088 089 090 /** 091 * The end-user verification code. 092 */ 093 private final UserCode userCode; 094 095 096 /** 097 * The end-user verification URI on the authorization server. The URI 098 * should be and easy to remember as end-users will be asked to 099 * manually type it into their user-agent. 100 */ 101 private final URI verificationURI; 102 103 104 /** 105 * Optional. A verification URI that includes the "user_code" (or other 106 * information with the same function as the "user_code"), designed for 107 * non-textual transmission. 108 */ 109 private final URI verificationURIComplete; 110 111 112 /** 113 * The lifetime in seconds of the "device_code" and "user_code". 114 */ 115 private final long lifetime; 116 117 118 /** 119 * Optional. The minimum amount of time in seconds that the client 120 * SHOULD wait between polling requests to the token endpoint. If no 121 * value is provided, clients MUST use 5 as the default. 122 */ 123 private final long interval; 124 125 126 /** 127 * Optional custom parameters. 128 */ 129 private final Map<String, Object> customParams; 130 131 132 /** 133 * Creates a new device authorization success response. 134 * 135 * @param deviceCode The device verification code. Must not be 136 * {@code null}. 137 * @param userCode The user verification code. Must not be 138 * {@code null}. 139 * @param verificationURI The end-user verification URI on the 140 * authorization server. Must not be 141 * {@code null}. 142 * @param lifetime The lifetime in seconds of the "device_code" 143 * and "user_code". 144 */ 145 public DeviceAuthorizationSuccessResponse(final DeviceCode deviceCode, 146 final UserCode userCode, 147 final URI verificationURI, 148 final long lifetime) { 149 150 this(deviceCode, userCode, verificationURI, null, lifetime, 5, null); 151 } 152 153 154 /** 155 * Creates a new device authorization success response. 156 * 157 * @param deviceCode The device verification code. Must 158 * not be {@code null}. 159 * @param userCode The user verification code. Must not 160 * be {@code null}. 161 * @param verificationURI The end-user verification URI on the 162 * authorization server. Must not be 163 * {@code null}. 164 * @param verificationURIComplete The end-user verification URI on the 165 * authorization server that includes 166 * the user_code. Can be {@code null}. 167 * @param lifetime The lifetime in seconds of the 168 * "device_code" and "user_code". Must 169 * be greater than {@code 0}. 170 * @param interval The minimum amount of time in seconds 171 * that the client SHOULD wait between 172 * polling requests to the token 173 * endpoint. 174 * @param customParams Optional custom parameters, 175 * {@code null} if none. 176 */ 177 public DeviceAuthorizationSuccessResponse(final DeviceCode deviceCode, 178 final UserCode userCode, 179 final URI verificationURI, 180 final URI verificationURIComplete, 181 final long lifetime, 182 final long interval, 183 final Map<String, Object> customParams) { 184 185 this.deviceCode = Objects.requireNonNull(deviceCode); 186 this.userCode = Objects.requireNonNull(userCode); 187 this.verificationURI = Objects.requireNonNull(verificationURI); 188 189 this.verificationURIComplete = verificationURIComplete; 190 191 if (lifetime <= 0) 192 throw new IllegalArgumentException("The lifetime must be greater than 0"); 193 194 this.lifetime = lifetime; 195 this.interval = interval; 196 this.customParams = customParams; 197 } 198 199 200 /** 201 * Returns the registered (standard) OAuth 2.0 device authorization 202 * response parameter names. 203 * 204 * @return The registered OAuth 2.0 device authorization response 205 * parameter names, as a unmodifiable set. 206 */ 207 public static Set<String> getRegisteredParameterNames() { 208 209 return REGISTERED_PARAMETER_NAMES; 210 } 211 212 213 @Override 214 public boolean indicatesSuccess() { 215 216 return true; 217 } 218 219 220 /** 221 * Returns the device verification code. 222 * 223 * @return The device verification code. 224 */ 225 public DeviceCode getDeviceCode() { 226 227 return deviceCode; 228 } 229 230 231 /** 232 * Returns the end-user verification code. 233 * 234 * @return The end-user verification code. 235 */ 236 public UserCode getUserCode() { 237 238 return userCode; 239 } 240 241 242 /** 243 * Returns the end-user verification URI on the authorization server. 244 * 245 * @return The end-user verification URI on the authorization server. 246 */ 247 public URI getVerificationURI() { 248 249 return verificationURI; 250 } 251 252 253 /** 254 * @see #getVerificationURI() 255 */ 256 @Deprecated 257 public URI getVerificationUri() { 258 259 return getVerificationURI(); 260 } 261 262 263 /** 264 * Returns the end-user verification URI that includes the user_code. 265 * 266 * @return The end-user verification URI that includes the user_code, 267 * or {@code null} if not specified. 268 */ 269 public URI getVerificationURIComplete() { 270 271 return verificationURIComplete; 272 } 273 274 275 /** 276 * @see #getVerificationURIComplete() 277 */ 278 @Deprecated 279 public URI getVerificationUriComplete() { 280 281 return getVerificationURIComplete(); 282 } 283 284 285 /** 286 * Returns the lifetime in seconds of the "device_code" and "user_code". 287 * 288 * @return The lifetime in seconds of the "device_code" and "user_code". 289 */ 290 public long getLifetime() { 291 292 return lifetime; 293 } 294 295 296 /** 297 * Returns the minimum amount of time in seconds that the client SHOULD 298 * wait between polling requests to the token endpoint. 299 * 300 * @return The minimum amount of time in seconds that the client SHOULD 301 * wait between polling requests to the token endpoint. 302 */ 303 public long getInterval() { 304 305 return interval; 306 } 307 308 309 /** 310 * Returns the custom parameters. 311 * 312 * @return The custom parameters, as a unmodifiable map, empty map if 313 * none. 314 */ 315 public Map<String, Object> getCustomParameters() { 316 317 if (customParams == null) 318 return Collections.emptyMap(); 319 320 return Collections.unmodifiableMap(customParams); 321 } 322 323 324 /** 325 * Returns a JSON object representation of this device authorization 326 * response. 327 * 328 * <p> 329 * Example JSON object: 330 * 331 * <pre> 332 * { 333 * "device_code" : "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", 334 * "user_code" : "WDJB-MJHT", 335 * "verification_uri" : "https://example.com/device", 336 * "verification_uri_complete" : "https://example.com/device?user_code=WDJB-MJHT", 337 * "expires_in" : 1800, 338 * "interval" : 5 339 * } 340 * </pre> 341 * 342 * @return The JSON object. 343 */ 344 public JSONObject toJSONObject() { 345 346 JSONObject o = new JSONObject(); 347 o.put("device_code", getDeviceCode()); 348 o.put("user_code", getUserCode()); 349 o.put("verification_uri", getVerificationURI().toString()); 350 351 if (getVerificationURIComplete() != null) 352 o.put("verification_uri_complete", getVerificationURIComplete().toString()); 353 354 o.put("expires_in", getLifetime()); 355 356 if (getInterval() > 0) 357 o.put("interval", getInterval()); 358 359 if (customParams != null) 360 o.putAll(customParams); 361 362 return o; 363 } 364 365 366 @Override 367 public HTTPResponse toHTTPResponse() { 368 369 HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK); 370 371 httpResponse.setEntityContentType(ContentType.APPLICATION_JSON); 372 httpResponse.setCacheControl("no-store"); 373 httpResponse.setPragma("no-cache"); 374 375 httpResponse.setBody(toJSONObject().toString()); 376 377 return httpResponse; 378 } 379 380 381 /** 382 * Parses an device authorization response from the specified JSON 383 * object. 384 * 385 * @param jsonObject The JSON object to parse. Must not be {@code null}. 386 * 387 * @return The device authorization response. 388 * 389 * @throws ParseException If the JSON object couldn't be parsed to a 390 * device authorization response. 391 */ 392 public static DeviceAuthorizationSuccessResponse parse(final JSONObject jsonObject) throws ParseException { 393 394 DeviceCode deviceCode = new DeviceCode(JSONObjectUtils.getString(jsonObject, "device_code")); 395 UserCode userCode = new UserCode(JSONObjectUtils.getString(jsonObject, "user_code")); 396 URI verificationURI = JSONObjectUtils.getURI(jsonObject, "verification_uri"); 397 URI verificationURIComplete = JSONObjectUtils.getURI(jsonObject, "verification_uri_complete", null); 398 399 // Parse lifetime 400 long lifetime; 401 if (jsonObject.get("expires_in") instanceof Number) { 402 403 lifetime = JSONObjectUtils.getLong(jsonObject, "expires_in"); 404 } else { 405 String lifetimeStr = JSONObjectUtils.getString(jsonObject, "expires_in"); 406 407 try { 408 lifetime = Long.parseLong(lifetimeStr); 409 410 } catch (NumberFormatException e) { 411 412 throw new ParseException("Invalid expires_in parameter, must be integer"); 413 } 414 } 415 416 // Parse lifetime 417 long interval = 5; 418 if (jsonObject.containsKey("interval")) { 419 if (jsonObject.get("interval") instanceof Number) { 420 421 interval = JSONObjectUtils.getLong(jsonObject, "interval"); 422 } else { 423 String intervalStr = JSONObjectUtils.getString(jsonObject, "interval"); 424 425 try { 426 interval = Long.parseLong(intervalStr); 427 428 } catch (NumberFormatException e) { 429 430 throw new ParseException("Invalid interval parameter, must be integer"); 431 } 432 } 433 } 434 435 // Determine the custom param names 436 Set<String> customParamNames = new HashSet<>(jsonObject.keySet()); 437 customParamNames.removeAll(getRegisteredParameterNames()); 438 439 Map<String, Object> customParams = null; 440 441 if (!customParamNames.isEmpty()) { 442 443 customParams = new LinkedHashMap<>(); 444 445 for (String name : customParamNames) { 446 customParams.put(name, jsonObject.get(name)); 447 } 448 } 449 450 return new DeviceAuthorizationSuccessResponse(deviceCode, userCode, verificationURI, 451 verificationURIComplete, lifetime, interval, customParams); 452 } 453 454 455 /** 456 * Parses an device authorization response from the specified HTTP 457 * response. 458 * 459 * @param httpResponse The HTTP response. Must not be {@code null}. 460 * 461 * @return The device authorization response. 462 * 463 * @throws ParseException If the HTTP response couldn't be parsed to a 464 * device authorization response. 465 */ 466 public static DeviceAuthorizationSuccessResponse parse(final HTTPResponse httpResponse) throws ParseException { 467 468 httpResponse.ensureStatusCode(HTTPResponse.SC_OK); 469 JSONObject jsonObject = httpResponse.getBodyAsJSONObject(); 470 return parse(jsonObject); 471 } 472}