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 // Required in OIDC 829 URL redirectURI = ar.getRedirectionURI(); 830 831 if (redirectURI == null) 832 throw new ParseException("Missing \"redirect_uri\" parameter", 833 OAuth2Error.INVALID_REQUEST); 834 835 State state = ar.getState(); 836 837 ResponseType rt = ar.getResponseType(); 838 839 try { 840 OIDCResponseTypeValidator.validate(rt); 841 842 } catch (IllegalArgumentException e) { 843 844 throw new ParseException("Unsupported \"response_type\" parameter: " + e.getMessage(), 845 OAuth2Error.UNSUPPORTED_RESPONSE_TYPE, 846 redirectURI, state); 847 } 848 849 // Required in OIDC, must include "openid" parameter 850 Scope scope = ar.getScope(); 851 852 if (scope == null) 853 throw new ParseException("Missing \"scope\" parameter", 854 OAuth2Error.INVALID_REQUEST, 855 redirectURI, state); 856 857 if (! scope.contains(OIDCScopeValue.OPENID)) 858 throw new ParseException("The scope must include an \"openid\" token", 859 OAuth2Error.INVALID_REQUEST, 860 redirectURI, state); 861 862 ClientID clientID = ar.getClientID(); 863 864 865 // Parse the remaining OIDC parameters 866 Nonce nonce = Nonce.parse(params.get("nonce")); 867 868 // Nonce required in implicit flow 869 if (rt.impliesImplicitFlow() && nonce == null) 870 throw new ParseException("Missing \"nonce\" parameter: Required in implicit flow", 871 OAuth2Error.INVALID_REQUEST, 872 redirectURI, state); 873 874 Display display = null; 875 876 try { 877 display = Display.parse(params.get("display")); 878 879 } catch (ParseException e) { 880 881 throw new ParseException("Invalid \"display\" parameter: " + e.getMessage(), 882 OAuth2Error.INVALID_REQUEST, 883 redirectURI, state, e); 884 } 885 886 887 Prompt prompt = null; 888 889 try { 890 prompt = Prompt.parse(params.get("prompt")); 891 892 } catch (ParseException e) { 893 894 throw new ParseException("Invalid \"prompt\" parameter: " + e.getMessage(), 895 OAuth2Error.INVALID_REQUEST, 896 redirectURI, state, e); 897 } 898 899 900 String v = params.get("max_age"); 901 902 int maxAge = 0; 903 904 if (StringUtils.isNotBlank(v)) { 905 906 try { 907 maxAge = Integer.parseInt(v); 908 909 } catch (NumberFormatException e) { 910 911 throw new ParseException("Invalid \"max_age\" parameter: " + e.getMessage(), 912 OAuth2Error.INVALID_REQUEST, 913 redirectURI, state, e); 914 } 915 } 916 917 918 v = params.get("ui_locales"); 919 920 List<LangTag> uiLocales = null; 921 922 if (StringUtils.isNotBlank(v)) { 923 924 uiLocales = new LinkedList<LangTag>(); 925 926 StringTokenizer st = new StringTokenizer(v, " "); 927 928 while (st.hasMoreTokens()) { 929 930 try { 931 uiLocales.add(LangTag.parse(st.nextToken())); 932 933 } catch (LangTagException e) { 934 935 throw new ParseException("Invalid \"ui_locales\" parameter: " + e.getMessage(), 936 OAuth2Error.INVALID_REQUEST, 937 redirectURI, state, e); 938 } 939 } 940 } 941 942 943 v = params.get("claims_locales"); 944 945 List<LangTag> claimsLocales = null; 946 947 if (StringUtils.isNotBlank(v)) { 948 949 claimsLocales = new LinkedList<LangTag>(); 950 951 StringTokenizer st = new StringTokenizer(v, " "); 952 953 while (st.hasMoreTokens()) { 954 955 try { 956 claimsLocales.add(LangTag.parse(st.nextToken())); 957 958 } catch (LangTagException e) { 959 960 throw new ParseException("Invalid \"claims_locales\" parameter: " + e.getMessage(), 961 OAuth2Error.INVALID_REQUEST, 962 redirectURI, state, e); 963 } 964 } 965 } 966 967 968 v = params.get("id_token_hint"); 969 970 JWT idTokenHint = null; 971 972 if (StringUtils.isNotBlank(v)) { 973 974 try { 975 idTokenHint = JWTParser.parse(v); 976 977 } catch (java.text.ParseException e) { 978 979 throw new ParseException("Invalid \"id_token_hint\" parameter: " + e.getMessage(), 980 OAuth2Error.INVALID_REQUEST, 981 redirectURI, state, e); 982 } 983 } 984 985 String loginHint = params.get("login_hint"); 986 987 988 v = params.get("acr_values"); 989 990 List<ACR> acrValues = null; 991 992 if (StringUtils.isNotBlank(v)) { 993 994 acrValues = new LinkedList<ACR>(); 995 996 StringTokenizer st = new StringTokenizer(v, " "); 997 998 while (st.hasMoreTokens()) { 999 1000 acrValues.add(new ACR(st.nextToken())); 1001 } 1002 } 1003 1004 1005 v = params.get("claims"); 1006 1007 ClaimsRequest claims = null; 1008 1009 if (StringUtils.isNotBlank(v)) { 1010 1011 JSONObject jsonObject = null; 1012 1013 try { 1014 jsonObject = JSONObjectUtils.parseJSONObject(v); 1015 1016 } catch (ParseException e) { 1017 1018 throw new ParseException("Invalid \"claims\" parameter: " + e.getMessage(), 1019 OAuth2Error.INVALID_REQUEST, 1020 redirectURI, state, e); 1021 } 1022 1023 // Parse exceptions silently ignored 1024 claims = ClaimsRequest.parse(jsonObject); 1025 } 1026 1027 1028 v = params.get("request_uri"); 1029 1030 URL requestURI = null; 1031 1032 if (StringUtils.isNotBlank(v)) { 1033 1034 try { 1035 requestURI = new URL(v); 1036 1037 } catch (MalformedURLException e) { 1038 1039 throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), 1040 OAuth2Error.INVALID_REQUEST, 1041 redirectURI, state, e); 1042 } 1043 } 1044 1045 v = params.get("request"); 1046 1047 JWT requestObject = null; 1048 1049 if (StringUtils.isNotBlank(v)) { 1050 1051 // request_object and request_uri must not be defined at the same time 1052 if (requestURI != null) { 1053 1054 throw new ParseException("Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters", 1055 OAuth2Error.INVALID_REQUEST, 1056 redirectURI, state, null); 1057 } 1058 1059 try { 1060 requestObject = JWTParser.parse(v); 1061 1062 } catch (java.text.ParseException e) { 1063 1064 throw new ParseException("Invalid \"request_object\" parameter: " + e.getMessage(), 1065 OAuth2Error.INVALID_REQUEST, 1066 redirectURI, state, e); 1067 } 1068 } 1069 1070 1071 // Select the appropriate constructor 1072 1073 // Inline request object 1074 if (requestObject != null) 1075 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1076 display, prompt, maxAge, uiLocales, claimsLocales, 1077 idTokenHint, loginHint, acrValues, claims, requestObject); 1078 1079 // Request object by URL reference 1080 if (requestURI != null) 1081 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1082 display, prompt, maxAge, uiLocales, claimsLocales, 1083 idTokenHint, loginHint, acrValues, claims, requestURI); 1084 1085 // No request object or URI 1086 return new OIDCAuthorizationRequest(uri, rt, scope, clientID, redirectURI, state, nonce, 1087 display, prompt, maxAge, uiLocales, claimsLocales, 1088 idTokenHint, loginHint, acrValues, claims); 1089 } 1090 1091 1092 /** 1093 * Parses an OpenID Connect authorisation request from the specified 1094 * URL query string. 1095 * 1096 * <p>Example URL query string: 1097 * 1098 * <pre> 1099 * response_type=token%20id_token 1100 * &client_id=s6BhdRkqt3 1101 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1102 * &scope=openid%20profile 1103 * &state=af0ifjsldkj 1104 * &nonce=n-0S6_WzA2Mj 1105 * </pre> 1106 * 1107 * @param uri The URI of the authorisation endpoint. May be 1108 * {@code null} if the {@link #toHTTPRequest()} method 1109 * will not be used. 1110 * @param query The URL query string. Must not be {@code null}. 1111 * 1112 * @return The OpenID Connect authorisation request. 1113 * 1114 * @throws ParseException If the query string couldn't be parsed to an 1115 * OpenID Connect authorisation request. 1116 */ 1117 public static OIDCAuthorizationRequest parse(final URL uri, final String query) 1118 throws ParseException { 1119 1120 return parse(uri, URLUtils.parseParameters(query)); 1121 } 1122 1123 1124 /** 1125 * Parses an authorisation request from the specified HTTP GET or HTTP 1126 * POST request. 1127 * 1128 * <p>Example HTTP request (GET): 1129 * 1130 * <pre> 1131 * https://server.example.com/op/authorize? 1132 * response_type=code%20id_token 1133 * &client_id=s6BhdRkqt3 1134 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1135 * &scope=openid 1136 * &nonce=n-0S6_WzA2Mj 1137 * &state=af0ifjsldkj 1138 * </pre> 1139 * 1140 * @param httpRequest The HTTP request. Must not be {@code null}. 1141 * 1142 * @return The OpenID Connect authorisation request. 1143 * 1144 * @throws ParseException If the HTTP request couldn't be parsed to an 1145 * OpenID Connect authorisation request. 1146 */ 1147 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1148 throws ParseException { 1149 1150 String query = httpRequest.getQuery(); 1151 1152 if (query == null) 1153 throw new ParseException("Missing URL query string"); 1154 1155 return parse(httpRequest.getURL(), query); 1156 } 1157}