001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URL; 006import java.util.Collections; 007import java.util.LinkedList; 008import java.util.List; 009import java.util.Map; 010import java.util.StringTokenizer; 011 012import net.jcip.annotations.Immutable; 013 014import org.apache.commons.lang3.StringUtils; 015 016import net.minidev.json.JSONObject; 017 018import com.nimbusds.langtag.LangTag; 019import com.nimbusds.langtag.LangTagException; 020 021import com.nimbusds.jwt.JWT; 022import com.nimbusds.jwt.JWTParser; 023 024import com.nimbusds.oauth2.sdk.AuthorizationRequest; 025import com.nimbusds.oauth2.sdk.OAuth2Error; 026import com.nimbusds.oauth2.sdk.ParseException; 027import com.nimbusds.oauth2.sdk.ResponseType; 028import com.nimbusds.oauth2.sdk.Scope; 029import com.nimbusds.oauth2.sdk.SerializeException; 030import com.nimbusds.oauth2.sdk.id.ClientID; 031import com.nimbusds.oauth2.sdk.id.State; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 034import com.nimbusds.oauth2.sdk.util.URLUtils; 035 036import com.nimbusds.openid.connect.sdk.claims.ACR; 037 038 039/** 040 * OpenID Connect authorisation request. Used to authenticate (if required) an 041 * end-user and request the end-user's authorisation to release information to 042 * the client. This class is immutable. 043 * 044 * <p>Example HTTP request (code flow): 045 * 046 * <pre> 047 * https://server.example.com/op/authorize? 048 * response_type=code%20id_token 049 * &client_id=s6BhdRkqt3 050 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 051 * &scope=openid 052 * &nonce=n-0S6_WzA2Mj 053 * &state=af0ifjsldkj 054 * </pre> 055 * 056 * <p>Related specifications: 057 * 058 * <ul> 059 * <li>OpenID Connect Messages 1.0, section 2.1.1. 060 * <li>OpenID Connect Standard 1.0, section 2.3.1. 061 * </ul> 062 * 063 * @author Vladimir Dzhuvinov 064 */ 065@Immutable 066public final class OIDCAuthorizationRequest extends AuthorizationRequest { 067 068 069 /** 070 * The nonce (required for implicit flow, optional for code flow). 071 */ 072 private final Nonce nonce; 073 074 075 /** 076 * The requested display type (optional). 077 */ 078 private final Display display; 079 080 081 /** 082 * The requested prompt (optional). 083 */ 084 private final Prompt prompt; 085 086 087 /** 088 * The required maximum authentication age, in seconds, 0 if not 089 * specified (optional). 090 */ 091 private final int maxAge; 092 093 094 /** 095 * The end-user's preferred languages and scripts for the user 096 * interface (optional). 097 */ 098 private final List<LangTag> uiLocales; 099 100 101 /** 102 * The end-user's preferred languages and scripts for claims being 103 * returned (optional). 104 */ 105 private final List<LangTag> claimsLocales; 106 107 108 /** 109 * Previously issued ID Token passed to the authorisation server as a 110 * hint about the end-user's current or past authenticated session with 111 * the client (optional). Should be present when {@code prompt=none} is 112 * used. 113 */ 114 private final JWT idTokenHint; 115 116 117 /** 118 * Hint to the authorisation server about the login identifier the 119 * end-user may use to log in (optional). 120 */ 121 private final String loginHint; 122 123 124 /** 125 * Requested Authentication Context Class Reference values (optional). 126 */ 127 private final List<ACR> acrValues; 128 129 130 /** 131 * Individual claims to be returned (optional). 132 */ 133 private final ClaimsRequest claims; 134 135 136 /** 137 * Request object (optional). 138 */ 139 private final JWT requestObject; 140 141 142 /** 143 * Request object URL (optional). 144 */ 145 private final URL requestURI; 146 147 148 /** 149 * Creates a new minimal OpenID Connect authorisation request. 150 * 151 * @param uri The URI of the authorisation endpoint. May be 152 * {@code null} if the {@link #toHTTPRequest()} 153 * method will not be used. 154 * @param rt The response type. Corresponds to the 155 * {@code response_type} parameter. Must specify a 156 * valid OpenID Connect response type. Must not be 157 * {@code null}. 158 * @param scope The request scope. Corresponds to the 159 * {@code scope} parameter. Must contain an 160 * {@link OIDCScopeValue#OPENID openid value}. Must 161 * not be {@code null}. 162 * @param clientID The client identifier. Corresponds to the 163 * {@code client_id} parameter. Must not be 164 * {@code null}. 165 * @param redirectURI The redirection URI. Corresponds to the 166 * {@code redirect_uri} parameter. Must not be 167 * {@code null}. 168 * @param state The state. Corresponds to the {@code state} 169 * parameter. May be {@code null}. 170 * @param nonce The nonce. Corresponds to the {@code nonce} 171 * parameter. May be {@code null} for code flow. 172 */ 173 public OIDCAuthorizationRequest(final URL uri, 174 final ResponseType rt, 175 final Scope scope, 176 final ClientID clientID, 177 final URL redirectURI, 178 final State state, 179 final Nonce nonce) { 180 181 // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 182 // idTokenHint, loginHint, acrValues, claims 183 this(uri, rt, scope, clientID, redirectURI, state, nonce, 184 null, null, 0, null, null, 185 null, null, null, null); 186 } 187 188 189 /** 190 * Creates a new OpenID Connect authorisation request without a request 191 * object. 192 * 193 * @param uri The URI of the authorisation endpoint. May be 194 * {@code null} if the {@link #toHTTPRequest()} 195 * method will not be used. 196 * @param rt The response type. Corresponds to the 197 * {@code response_type} parameter. Must specify a 198 * valid OpenID Connect response type. Must not be 199 * {@code null}. 200 * @param scope The request scope. Corresponds to the 201 * {@code scope} parameter. Must contain an 202 * {@link OIDCScopeValue#OPENID openid value}. 203 * Must not be {@code null}. 204 * @param clientID The client identifier. Corresponds to the 205 * {@code client_id} parameter. Must not be 206 * {@code null}. 207 * @param redirectURI The redirection URI. Corresponds to the 208 * {@code redirect_uri} parameter. Must not be 209 * {@code null}. 210 * @param state The state. Corresponds to the recommended 211 * {@code state} parameter. {@code null} if not 212 * specified. 213 * @param nonce The nonce. Corresponds to the {@code nonce} 214 * parameter. May be {@code null} for code flow. 215 * @param display The requested display type. Corresponds to the 216 * optional {@code display} parameter. 217 * {@code null} if not specified. 218 * @param prompt The requested prompt. Corresponds to the 219 * optional {@code prompt} parameter. {@code null} 220 * if not specified. 221 * @param maxAge The required maximum authentication age, in 222 * seconds. Corresponds to the optional 223 * {@code max_age} parameter. Zero if not 224 * specified. 225 * @param uiLocales The preferred languages and scripts for the 226 * user interface. Corresponds to the optional 227 * {@code ui_locales} parameter. {@code null} if 228 * not specified. 229 * @param claimsLocales The preferred languages and scripts for claims 230 * being returned. Corresponds to the optional 231 * {@code claims_locales} parameter. {@code null} 232 * if not specified. 233 * @param idTokenHint The ID Token hint. Corresponds to the optional 234 * {@code id_token_hint} parameter. {@code null} 235 * if not specified. 236 * @param loginHint The login hint. Corresponds to the optional 237 * {@code login_hint} parameter. {@code null} if 238 * not specified. 239 * @param acrValues The requested Authentication Context Class 240 * Reference values. Corresponds to the optional 241 * {@code acr_values} parameter. {@code null} if 242 * not specified. 243 * @param claims The individual claims to be returned. 244 * Corresponds to the optional {@code claims} 245 * parameter. {@code null} if not specified. 246 */ 247 public OIDCAuthorizationRequest(final URL uri, 248 final ResponseType rt, 249 final Scope scope, 250 final ClientID clientID, 251 final URL redirectURI, 252 final State state, 253 final Nonce nonce, 254 final Display display, 255 final Prompt prompt, 256 final int maxAge, 257 final List<LangTag> uiLocales, 258 final List<LangTag> claimsLocales, 259 final JWT idTokenHint, 260 final String loginHint, 261 final List<ACR> acrValues, 262 final ClaimsRequest claims) { 263 264 265 this(uri, rt, scope, clientID, redirectURI, state, nonce, display, prompt, 266 maxAge, uiLocales, claimsLocales, idTokenHint, loginHint, acrValues, 267 claims, (JWT)null); 268 } 269 270 271 /** 272 * Creates a new OpenID Connect authorisation request with a request 273 * object specified by value. 274 * 275 * @param uri The URI of the authorisation endpoint. May be 276 * {@code null} if the {@link #toHTTPRequest()} 277 * method will not be used. 278 * @param rt The response type set. Corresponds to the 279 * {@code response_type} parameter. Must specify a 280 * valid OpenID Connect response type. Must not be 281 * {@code null}. 282 * @param scope The request scope. Corresponds to the 283 * {@code scope} parameter. Must contain an 284 * {@link OIDCScopeValue#OPENID openid value}. 285 * Must not be {@code null}. 286 * @param clientID The client identifier. Corresponds to the 287 * {@code client_id} parameter. Must not be 288 * {@code null}. 289 * @param redirectURI The redirection URI. Corresponds to the 290 * {@code redirect_uri} parameter. Must not be 291 * {@code null}. 292 * @param state The state. Corresponds to the recommended 293 * {@code state} parameter. {@code null} if not 294 * specified. 295 * @param nonce The nonce. Corresponds to the {@code nonce} 296 * parameter. May be {@code null} for code flow. 297 * @param display The requested display type. Corresponds to the 298 * optional {@code display} parameter. 299 * {@code null} if not specified. 300 * @param prompt The requested prompt. Corresponds to the 301 * optional {@code prompt} parameter. {@code null} 302 * if not specified. 303 * @param maxAge The required maximum authentication age, in 304 * seconds. Corresponds to the optional 305 * {@code max_age} parameter. Zero if not 306 * specified. 307 * @param uiLocales The preferred languages and scripts for the 308 * user interface. Corresponds to the optional 309 * {@code ui_locales} parameter. {@code null} if 310 * not specified. 311 * @param claimsLocales The preferred languages and scripts for claims 312 * being returned. Corresponds to the optional 313 * {@code claims_locales} parameter. {@code null} 314 * if not specified. 315 * @param idTokenHint The ID Token hint. Corresponds to the optional 316 * {@code id_token_hint} parameter. {@code null} 317 * if not specified. 318 * @param loginHint The login hint. Corresponds to the optional 319 * {@code login_hint} parameter. {@code null} if 320 * not specified. 321 * @param acrValues The requested Authentication Context Class 322 * Reference values. Corresponds to the optional 323 * {@code acr_values} parameter. {@code null} if 324 * not specified. 325 * @param claims The individual claims to be returned. 326 * Corresponds to the optional {@code claims} 327 * parameter. {@code null} if not specified. 328 * @param requestObject The request object. Corresponds to the optional 329 * {@code request} parameter. {@code null} if not 330 * specified. 331 */ 332 public OIDCAuthorizationRequest(final URL uri, 333 final ResponseType rt, 334 final Scope scope, 335 final ClientID clientID, 336 final URL redirectURI, 337 final State state, 338 final Nonce nonce, 339 final Display display, 340 final Prompt prompt, 341 final int maxAge, 342 final List<LangTag> uiLocales, 343 final List<LangTag> claimsLocales, 344 final JWT idTokenHint, 345 final String loginHint, 346 final List<ACR> acrValues, 347 final ClaimsRequest claims, 348 final JWT requestObject) { 349 350 super(uri, rt, clientID, redirectURI, scope, state); 351 352 if (redirectURI == null) 353 throw new IllegalArgumentException("The redirect URI must not be null"); 354 355 OIDCResponseTypeValidator.validate(rt); 356 357 if (scope == null) 358 throw new IllegalArgumentException("The scope must not be null"); 359 360 if (! scope.contains(OIDCScopeValue.OPENID)) 361 throw new IllegalArgumentException("The scope must include an \"openid\" token"); 362 363 364 // Nonce required for implicit protocol flow 365 if (rt.impliesImplicitFlow() && nonce == null) 366 throw new IllegalArgumentException("Nonce is required in implicit protocol flow"); 367 368 this.nonce = nonce; 369 370 // Optional parameters 371 this.display = display; 372 this.prompt = prompt; 373 this.maxAge = maxAge; 374 375 if (uiLocales != null) 376 this.uiLocales = Collections.unmodifiableList(uiLocales); 377 else 378 this.uiLocales = null; 379 380 if (claimsLocales != null) 381 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 382 else 383 this.claimsLocales = null; 384 385 this.idTokenHint = idTokenHint; 386 this.loginHint = loginHint; 387 388 if (acrValues != null) 389 this.acrValues = Collections.unmodifiableList(acrValues); 390 else 391 this.acrValues = null; 392 393 this.claims = claims; 394 this.requestObject = requestObject; 395 this.requestURI = null; 396 } 397 398 399 /** 400 * Creates a new OpenID Connect authorisation request with a request 401 * object specified by URL. 402 * 403 * @param uri The URI of the authorisation endpoint. May be 404 * {@code null} if the {@link #toHTTPRequest()} 405 * method will not be used. 406 * @param rt The response type. Corresponds to the 407 * {@code response_type} parameter. Must specify a 408 * a valid OpenID Connect response type. Must not 409 * be {@code null}. 410 * @param scope The request scope. Corresponds to the 411 * {@code scope} parameter. Must contain an 412 * {@link OIDCScopeValue#OPENID openid value}. 413 * Must not be {@code null}. 414 * @param clientID The client identifier. Corresponds to the 415 * {@code client_id} parameter. Must not be 416 * {@code null}. 417 * @param redirectURI The redirection URI. Corresponds to the 418 * {@code redirect_uri} parameter. Must not be 419 * {@code null}. 420 * @param state The state. Corresponds to the recommended 421 * {@code state} parameter. {@code null} if not 422 * specified. 423 * @param nonce The nonce. Corresponds to the {@code nonce} 424 * parameter. May be {@code null} for code flow. 425 * @param display The requested display type. Corresponds to the 426 * optional {@code display} parameter. 427 * {@code null} if not specified. 428 * @param prompt The requested prompt. Corresponds to the 429 * optional {@code prompt} parameter. {@code null} 430 * if not specified. 431 * @param maxAge The required maximum authentication age, in 432 * seconds. Corresponds to the optional 433 * {@code max_age} parameter. Zero if not 434 * specified. 435 * @param uiLocales The preferred languages and scripts for the 436 * user interface. Corresponds to the optional 437 * {@code ui_locales} parameter. {@code null} if 438 * not specified. 439 * @param claimsLocales The preferred languages and scripts for claims 440 * being returned. Corresponds to the optional 441 * {@code claims_locales} parameter. {@code null} 442 * if not specified. 443 * @param idTokenHint The ID Token hint. Corresponds to the optional 444 * {@code id_token_hint} parameter. {@code null} 445 * if not specified. 446 * @param loginHint The login hint. Corresponds to the optional 447 * {@code login_hint} parameter. {@code null} if 448 * not specified. 449 * @param acrValues The requested Authentication Context Class 450 * Reference values. Corresponds to the optional 451 * {@code acr_values} parameter. {@code null} if 452 * not specified. 453 * @param claims The individual claims to be returned. 454 * Corresponds to the optional {@code claims} 455 * parameter. {@code null} if not specified. 456 * @param requestURI The request object URL. Corresponds to the 457 * optional {@code request_uri} parameter. 458 * {@code null} if not specified. 459 */ 460 public OIDCAuthorizationRequest(final URL uri, 461 final ResponseType rt, 462 final Scope scope, 463 final ClientID clientID, 464 final URL redirectURI, 465 final State state, 466 final Nonce nonce, 467 final Display display, 468 final Prompt prompt, 469 final int maxAge, 470 final List<LangTag> uiLocales, 471 final List<LangTag> claimsLocales, 472 final JWT idTokenHint, 473 final String loginHint, 474 final List<ACR> acrValues, 475 final ClaimsRequest claims, 476 final URL requestURI) { 477 478 super(uri, rt, clientID, redirectURI, scope, state); 479 480 if (redirectURI == null) 481 throw new IllegalArgumentException("The redirect URI must not be null"); 482 483 OIDCResponseTypeValidator.validate(rt); 484 485 if (scope == null) 486 throw new IllegalArgumentException("The scope must not be null"); 487 488 if (! scope.contains(OIDCScopeValue.OPENID)) 489 throw new IllegalArgumentException("The scope must include an \"openid\" token"); 490 491 492 // Nonce required for implicit protocol flow 493 if (rt.impliesImplicitFlow() && nonce == null) 494 throw new IllegalArgumentException("Nonce is required in implicit protocol flow"); 495 496 this.nonce = nonce; 497 498 // Optional parameters 499 // Optional parameters 500 this.display = display; 501 this.prompt = prompt; 502 this.maxAge = maxAge; 503 504 if (uiLocales != null) 505 this.uiLocales = Collections.unmodifiableList(uiLocales); 506 else 507 this.uiLocales = null; 508 509 if (claimsLocales != null) 510 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 511 else 512 this.claimsLocales = null; 513 514 this.idTokenHint = idTokenHint; 515 this.loginHint = loginHint; 516 517 if (acrValues != null) 518 this.acrValues = Collections.unmodifiableList(acrValues); 519 else 520 this.acrValues = null; 521 522 this.claims = claims; 523 this.requestObject = null; 524 this.requestURI = requestURI; 525 } 526 527 528 /** 529 * Gets the nonce. Corresponds to the conditionally optional 530 * {@code nonce} parameter. 531 * 532 * @return The nonce, {@code null} if not specified. 533 */ 534 public Nonce getNonce() { 535 536 return nonce; 537 } 538 539 540 /** 541 * Gets the requested display type. Corresponds to the optional 542 * {@code display} parameter. 543 * 544 * @return The requested display type, {@code null} if not specified. 545 */ 546 public Display getDisplay() { 547 548 return display; 549 } 550 551 552 /** 553 * Gets the requested prompt. Corresponds to the optional 554 * {@code prompt} parameter. 555 * 556 * @return The requested prompt, {@code null} if not specified. 557 */ 558 public Prompt getPrompt() { 559 560 return prompt; 561 } 562 563 564 /** 565 * Gets the required maximum authentication age. Corresponds to the 566 * optional {@code max_age} parameter. 567 * 568 * @return The maximum authentication age, in seconds; 0 if not 569 * specified. 570 */ 571 public int getMaxAge() { 572 573 return maxAge; 574 } 575 576 577 /** 578 * Gets the end-user's preferred languages and scripts for the user 579 * interface, ordered by preference. Corresponds to the optional 580 * {@code ui_locales} parameter. 581 * 582 * @return The preferred UI locales, {@code null} if not specified. 583 */ 584 public List<LangTag> getUILocales() { 585 586 return uiLocales; 587 } 588 589 590 /** 591 * Gets the end-user's preferred languages and scripts for the claims 592 * being returned, ordered by preference. Corresponds to the optional 593 * {@code claims_locales} parameter. 594 * 595 * @return The preferred claims locales, {@code null} if not specified. 596 */ 597 public List<LangTag> getClaimsLocales() { 598 599 return claimsLocales; 600 } 601 602 603 /** 604 * Gets the ID Token hint. Corresponds to the conditionally optional 605 * {@code id_token_hint} parameter. 606 * 607 * @return The ID Token hint, {@code null} if not specified. 608 */ 609 public JWT getIDTokenHint() { 610 611 return idTokenHint; 612 } 613 614 615 /** 616 * Gets the login hint. Corresponds to the optional {@code login_hint} 617 * parameter. 618 * 619 * @return The login hint, {@code null} if not specified. 620 */ 621 public String getLoginHint() { 622 623 return loginHint; 624 } 625 626 627 /** 628 * Gets the requested Authentication Context Class Reference values. 629 * Corresponds to the optional {@code acr_values} parameter. 630 * 631 * @return The requested ACR values, {@code null} if not specified. 632 */ 633 public List<ACR> getACRValues() { 634 635 return acrValues; 636 } 637 638 639 /** 640 * Gets the individual claims to be returned. Corresponds to the 641 * optional {@code claims} parameter. 642 * 643 * @return The individual claims to be returned, {@code null} if not 644 * specified. 645 */ 646 public ClaimsRequest getClaims() { 647 648 return claims; 649 } 650 651 652 /** 653 * Gets the request object. Corresponds to the optional {@code request} 654 * parameter. 655 * 656 * @return The request object, {@code null} if not specified. 657 */ 658 public JWT getRequestObject() { 659 660 return requestObject; 661 } 662 663 664 /** 665 * Gets the request object URL. Corresponds to the optional 666 * {@code request_uri} parameter. 667 * 668 * @return The request object URL, {@code null} if not specified. 669 */ 670 public URL getRequestURI() { 671 672 return requestURI; 673 } 674 675 676 /** 677 * Returns {@code true} if this authorisation request specifies an 678 * OpenID Connect request object (directly through the {@code request} 679 * parameter or by reference through the {@code request_uri} parameter). 680 * 681 * @return {@code true} if a request object is specified, else 682 * {@code false}. 683 */ 684 public boolean specifiesRequestObject() { 685 686 if (requestObject != null || requestURI != null) { 687 688 return true; 689 690 } else { 691 692 return false; 693 } 694 } 695 696 697 @Override 698 public Map<String,String> toParameters() 699 throws SerializeException { 700 701 Map <String,String> params = super.toParameters(); 702 703 if (nonce != null) 704 params.put("nonce", nonce.toString()); 705 706 if (display != null) 707 params.put("display", display.toString()); 708 709 if (prompt != null) 710 params.put("prompt", prompt.toString()); 711 712 if (maxAge > 0) 713 params.put("max_age", "" + maxAge); 714 715 if (uiLocales != null) { 716 717 StringBuilder sb = new StringBuilder(); 718 719 for (LangTag locale: uiLocales) { 720 721 if (sb.length() > 0) 722 sb.append(' '); 723 724 sb.append(locale.toString()); 725 } 726 727 params.put("ui_locales", sb.toString()); 728 } 729 730 if (claimsLocales != null) { 731 732 StringBuilder sb = new StringBuilder(); 733 734 for (LangTag locale: claimsLocales) { 735 736 if (sb.length() > 0) 737 sb.append(' '); 738 739 sb.append(locale.toString()); 740 } 741 742 params.put("claims_locales", sb.toString()); 743 } 744 745 if (idTokenHint != null) { 746 747 try { 748 params.put("id_token_hint", idTokenHint.serialize()); 749 750 } catch (IllegalStateException e) { 751 752 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 753 } 754 } 755 756 if (loginHint != null) 757 params.put("login_hint", loginHint); 758 759 if (acrValues != null) { 760 761 StringBuilder sb = new StringBuilder(); 762 763 for (ACR acr: acrValues) { 764 765 if (sb.length() > 0) 766 sb.append(' '); 767 768 sb.append(acr.toString()); 769 } 770 771 params.put("acr_values", sb.toString()); 772 } 773 774 775 if (claims != null) 776 params.put("claims", claims.toJSONObject().toString()); 777 778 if (requestObject != null) { 779 780 try { 781 params.put("request", requestObject.serialize()); 782 783 } catch (IllegalStateException e) { 784 785 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 786 } 787 } 788 789 if (requestURI != null) 790 params.put("request_uri", requestURI.toString()); 791 792 return params; 793 } 794 795 796 /** 797 * Parses an OpenID Connect authorisation request from the specified 798 * parameters. 799 * 800 * <p>Example parameters: 801 * 802 * <pre> 803 * response_type = token id_token 804 * client_id = s6BhdRkqt3 805 * redirect_uri = https://client.example.com/cb 806 * scope = openid profile 807 * state = af0ifjsldkj 808 * nonce = -0S6_WzA2Mj 809 * </pre> 810 * 811 * @param uri The URI of the authorisation endpoint. May be 812 * {@code null} if the {@link #toHTTPRequest()} method 813 * will not be used. 814 * @param params The parameters. Must not be {@code null}. 815 * 816 * @return The OpenID Connect authorisation request. 817 * 818 * @throws ParseException If the parameters couldn't be parsed to an 819 * OpenID Connect authorisation request. 820 */ 821 public static OIDCAuthorizationRequest parse(final URL uri, final Map<String,String> params) 822 throws ParseException { 823 824 // Parse and validate the core OAuth 2.0 autz request params in 825 // the context of OIDC 826 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 827 828 ClientID clientID = ar.getClientID(); 829 State state = ar.getState(); 830 831 // Required in OIDC 832 URL redirectURI = ar.getRedirectionURI(); 833 834 if (redirectURI == null) 835 throw new ParseException("Missing \"redirect_uri\" parameter", 836 OAuth2Error.INVALID_REQUEST, clientID, null, state); 837 838 839 ResponseType rt = ar.getResponseType(); 840 841 try { 842 OIDCResponseTypeValidator.validate(rt); 843 844 } catch (IllegalArgumentException e) { 845 846 throw new ParseException("Unsupported \"response_type\" parameter: " + e.getMessage(), 847 OAuth2Error.UNSUPPORTED_RESPONSE_TYPE, 848 clientID, redirectURI, state); 849 } 850 851 // Required in OIDC, must include "openid" parameter 852 Scope scope = ar.getScope(); 853 854 if (scope == null) 855 throw new ParseException("Missing \"scope\" parameter", 856 OAuth2Error.INVALID_REQUEST, 857 clientID, redirectURI, state); 858 859 if (! scope.contains(OIDCScopeValue.OPENID)) 860 throw new ParseException("The scope must include an \"openid\" token", 861 OAuth2Error.INVALID_REQUEST, 862 clientID, redirectURI, state); 863 864 865 866 867 // Parse the remaining OIDC parameters 868 Nonce nonce = Nonce.parse(params.get("nonce")); 869 870 // Nonce required in implicit flow 871 if (rt.impliesImplicitFlow() && nonce == null) 872 throw new ParseException("Missing \"nonce\" parameter: Required in implicit flow", 873 OAuth2Error.INVALID_REQUEST, 874 clientID, redirectURI, state); 875 876 Display display; 877 878 try { 879 display = Display.parse(params.get("display")); 880 881 } catch (ParseException e) { 882 883 throw new ParseException("Invalid \"display\" parameter: " + e.getMessage(), 884 OAuth2Error.INVALID_REQUEST, 885 clientID, redirectURI, state, e); 886 } 887 888 889 Prompt prompt; 890 891 try { 892 prompt = Prompt.parse(params.get("prompt")); 893 894 } catch (ParseException e) { 895 896 throw new ParseException("Invalid \"prompt\" parameter: " + e.getMessage(), 897 OAuth2Error.INVALID_REQUEST, 898 clientID, redirectURI, state, e); 899 } 900 901 902 String v = params.get("max_age"); 903 904 int maxAge = 0; 905 906 if (StringUtils.isNotBlank(v)) { 907 908 try { 909 maxAge = Integer.parseInt(v); 910 911 } catch (NumberFormatException e) { 912 913 throw new ParseException("Invalid \"max_age\" parameter: " + e.getMessage(), 914 OAuth2Error.INVALID_REQUEST, 915 clientID, redirectURI, state, e); 916 } 917 } 918 919 920 v = params.get("ui_locales"); 921 922 List<LangTag> uiLocales = null; 923 924 if (StringUtils.isNotBlank(v)) { 925 926 uiLocales = new LinkedList<LangTag>(); 927 928 StringTokenizer st = new StringTokenizer(v, " "); 929 930 while (st.hasMoreTokens()) { 931 932 try { 933 uiLocales.add(LangTag.parse(st.nextToken())); 934 935 } catch (LangTagException e) { 936 937 throw new ParseException("Invalid \"ui_locales\" parameter: " + e.getMessage(), 938 OAuth2Error.INVALID_REQUEST, 939 clientID, redirectURI, state, e); 940 } 941 } 942 } 943 944 945 v = params.get("claims_locales"); 946 947 List<LangTag> claimsLocales = null; 948 949 if (StringUtils.isNotBlank(v)) { 950 951 claimsLocales = new LinkedList<LangTag>(); 952 953 StringTokenizer st = new StringTokenizer(v, " "); 954 955 while (st.hasMoreTokens()) { 956 957 try { 958 claimsLocales.add(LangTag.parse(st.nextToken())); 959 960 } catch (LangTagException e) { 961 962 throw new ParseException("Invalid \"claims_locales\" parameter: " + e.getMessage(), 963 OAuth2Error.INVALID_REQUEST, 964 clientID, redirectURI, state, e); 965 } 966 } 967 } 968 969 970 v = params.get("id_token_hint"); 971 972 JWT idTokenHint = null; 973 974 if (StringUtils.isNotBlank(v)) { 975 976 try { 977 idTokenHint = JWTParser.parse(v); 978 979 } catch (java.text.ParseException e) { 980 981 throw new ParseException("Invalid \"id_token_hint\" parameter: " + e.getMessage(), 982 OAuth2Error.INVALID_REQUEST, 983 clientID, redirectURI, state, e); 984 } 985 } 986 987 String loginHint = params.get("login_hint"); 988 989 990 v = params.get("acr_values"); 991 992 List<ACR> acrValues = null; 993 994 if (StringUtils.isNotBlank(v)) { 995 996 acrValues = new LinkedList<ACR>(); 997 998 StringTokenizer st = new StringTokenizer(v, " "); 999 1000 while (st.hasMoreTokens()) { 1001 1002 acrValues.add(new ACR(st.nextToken())); 1003 } 1004 } 1005 1006 1007 v = params.get("claims"); 1008 1009 ClaimsRequest claims = null; 1010 1011 if (StringUtils.isNotBlank(v)) { 1012 1013 JSONObject jsonObject; 1014 1015 try { 1016 jsonObject = JSONObjectUtils.parseJSONObject(v); 1017 1018 } catch (ParseException e) { 1019 1020 throw new ParseException("Invalid \"claims\" parameter: " + e.getMessage(), 1021 OAuth2Error.INVALID_REQUEST, 1022 clientID, redirectURI, state, e); 1023 } 1024 1025 // Parse exceptions silently ignored 1026 claims = ClaimsRequest.parse(jsonObject); 1027 } 1028 1029 1030 v = params.get("request_uri"); 1031 1032 URL requestURI = null; 1033 1034 if (StringUtils.isNotBlank(v)) { 1035 1036 try { 1037 requestURI = new URL(v); 1038 1039 } catch (MalformedURLException e) { 1040 1041 throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), 1042 OAuth2Error.INVALID_REQUEST, 1043 clientID, redirectURI, state, e); 1044 } 1045 } 1046 1047 v = params.get("request"); 1048 1049 JWT requestObject = null; 1050 1051 if (StringUtils.isNotBlank(v)) { 1052 1053 // request_object and request_uri must not be defined at the same time 1054 if (requestURI != null) { 1055 1056 throw new ParseException("Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters", 1057 OAuth2Error.INVALID_REQUEST, 1058 clientID, redirectURI, state, null); 1059 } 1060 1061 try { 1062 requestObject = JWTParser.parse(v); 1063 1064 } catch (java.text.ParseException e) { 1065 1066 throw new ParseException("Invalid \"request_object\" parameter: " + e.getMessage(), 1067 OAuth2Error.INVALID_REQUEST, 1068 clientID, redirectURI, state, e); 1069 } 1070 } 1071 1072 1073 // Select the appropriate constructor 1074 1075 // Inline request object 1076 if (requestObject != null) 1077 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1078 display, prompt, maxAge, uiLocales, claimsLocales, 1079 idTokenHint, loginHint, acrValues, claims, requestObject); 1080 1081 // Request object by URL reference 1082 if (requestURI != null) 1083 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1084 display, prompt, maxAge, uiLocales, claimsLocales, 1085 idTokenHint, loginHint, acrValues, claims, requestURI); 1086 1087 // No request object or URI 1088 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1089 display, prompt, maxAge, uiLocales, claimsLocales, 1090 idTokenHint, loginHint, acrValues, claims); 1091 } 1092 1093 1094 /** 1095 * Parses an OpenID Connect authorisation request from the specified 1096 * URL query string. 1097 * 1098 * <p>Example URL query string: 1099 * 1100 * <pre> 1101 * response_type=token%20id_token 1102 * &client_id=s6BhdRkqt3 1103 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1104 * &scope=openid%20profile 1105 * &state=af0ifjsldkj 1106 * &nonce=n-0S6_WzA2Mj 1107 * </pre> 1108 * 1109 * @param uri The URI of the authorisation endpoint. May be 1110 * {@code null} if the {@link #toHTTPRequest()} method 1111 * will not be used. 1112 * @param query The URL query string. Must not be {@code null}. 1113 * 1114 * @return The OpenID Connect authorisation request. 1115 * 1116 * @throws ParseException If the query string couldn't be parsed to an 1117 * OpenID Connect authorisation request. 1118 */ 1119 public static OIDCAuthorizationRequest parse(final URL uri, final String query) 1120 throws ParseException { 1121 1122 return parse(uri, URLUtils.parseParameters(query)); 1123 } 1124 1125 1126 /** 1127 * Parses an authorisation request from the specified HTTP GET or HTTP 1128 * POST request. 1129 * 1130 * <p>Example HTTP request (GET): 1131 * 1132 * <pre> 1133 * https://server.example.com/op/authorize? 1134 * response_type=code%20id_token 1135 * &client_id=s6BhdRkqt3 1136 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1137 * &scope=openid 1138 * &nonce=n-0S6_WzA2Mj 1139 * &state=af0ifjsldkj 1140 * </pre> 1141 * 1142 * @param httpRequest The HTTP request. Must not be {@code null}. 1143 * 1144 * @return The OpenID Connect authorisation request. 1145 * 1146 * @throws ParseException If the HTTP request couldn't be parsed to an 1147 * OpenID Connect authorisation request. 1148 */ 1149 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1150 throws ParseException { 1151 1152 String query = httpRequest.getQuery(); 1153 1154 if (query == null) 1155 throw new ParseException("Missing URL query string"); 1156 1157 return parse(httpRequest.getURL(), query); 1158 } 1159}