001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.*; 009 010import com.nimbusds.oauth2.sdk.http.HTTPRequest; 011import com.nimbusds.oauth2.sdk.id.ClientID; 012import com.nimbusds.oauth2.sdk.id.State; 013import com.nimbusds.oauth2.sdk.pkce.CodeChallenge; 014import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod; 015import com.nimbusds.oauth2.sdk.util.URIUtils; 016import com.nimbusds.oauth2.sdk.util.URLUtils; 017import net.jcip.annotations.Immutable; 018import org.apache.commons.collections4.MapUtils; 019import org.apache.commons.lang3.StringUtils; 020 021 022/** 023 * Authorisation request. Used to authenticate an end-user and request the 024 * end-user's consent to grant the client access to a protected resource. 025 * Supports custom request parameters. 026 * 027 * <p>Extending classes may define additional request parameters as well as 028 * enforce tighter requirements on the base parameters. 029 * 030 * <p>Example HTTP request: 031 * 032 * <pre> 033 * https://server.example.com/authorize? 034 * response_type=code 035 * &client_id=s6BhdRkqt3 036 * &state=xyz 037 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 038 * </pre> 039 * 040 * <p>Related specifications: 041 * 042 * <ul> 043 * <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1. 044 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 045 * <li>OAuth 2.0 Form Post Response Mode 1.0. 046 * <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636). 047 * </ul> 048 */ 049@Immutable 050public class AuthorizationRequest extends AbstractRequest { 051 052 053 /** 054 * The registered parameter names. 055 */ 056 private static final Set<String> REGISTERED_PARAMETER_NAMES; 057 058 059 /** 060 * Initialises the registered parameter name set. 061 */ 062 static { 063 Set<String> p = new HashSet<>(); 064 065 p.add("response_type"); 066 p.add("client_id"); 067 p.add("redirect_uri"); 068 p.add("scope"); 069 p.add("state"); 070 p.add("response_mode"); 071 p.add("code_challenge"); 072 p.add("code_challenge_method"); 073 074 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 075 } 076 077 078 /** 079 * The response type (required). 080 */ 081 private final ResponseType rt; 082 083 084 /** 085 * The client identifier (required). 086 */ 087 private final ClientID clientID; 088 089 090 /** 091 * The redirection URI where the response will be sent (optional). 092 */ 093 private final URI redirectURI; 094 095 096 /** 097 * The scope (optional). 098 */ 099 private final Scope scope; 100 101 102 /** 103 * The opaque value to maintain state between the request and the 104 * callback (recommended). 105 */ 106 private final State state; 107 108 109 /** 110 * The response mode (optional). 111 */ 112 private final ResponseMode rm; 113 114 115 /** 116 * The authorisation code challenge for PKCE (optional). 117 */ 118 private final CodeChallenge codeChallenge; 119 120 121 /** 122 * The authorisation code challenge method for PKCE (optional). 123 */ 124 private final CodeChallengeMethod codeChallengeMethod; 125 126 127 /** 128 * Additional custom parameters. 129 */ 130 private final Map<String,String> customParams; 131 132 133 /** 134 * Builder for constructing authorisation requests. 135 */ 136 public static class Builder { 137 138 139 /** 140 * The endpoint URI (optional). 141 */ 142 private URI uri; 143 144 145 /** 146 * The response type (required). 147 */ 148 private final ResponseType rt; 149 150 151 /** 152 * The client identifier (required). 153 */ 154 private final ClientID clientID; 155 156 157 /** 158 * The redirection URI where the response will be sent 159 * (optional). 160 */ 161 private URI redirectURI; 162 163 164 /** 165 * The scope (optional). 166 */ 167 private Scope scope; 168 169 170 /** 171 * The opaque value to maintain state between the request and 172 * the callback (recommended). 173 */ 174 private State state; 175 176 177 /** 178 * The response mode (optional). 179 */ 180 private ResponseMode rm; 181 182 183 /** 184 * The authorisation code challenge for PKCE (optional). 185 */ 186 private CodeChallenge codeChallenge; 187 188 189 /** 190 * The authorisation code challenge method for PKCE (optional). 191 */ 192 private CodeChallengeMethod codeChallengeMethod; 193 194 195 /** 196 * The additional custom parameters. 197 */ 198 private Map<String,String> customParams = new HashMap<>(); 199 200 201 /** 202 * Creates a new authorisation request builder. 203 * 204 * @param rt The response type. Corresponds to the 205 * {@code response_type} parameter. Must not be 206 * {@code null}. 207 * @param clientID The client identifier. Corresponds to the 208 * {@code client_id} parameter. Must not be 209 * {@code null}. 210 */ 211 public Builder(final ResponseType rt, final ClientID clientID) { 212 213 if (rt == null) 214 throw new IllegalArgumentException("The response type must not be null"); 215 216 this.rt = rt; 217 218 219 if (clientID == null) 220 throw new IllegalArgumentException("The client ID must not be null"); 221 222 this.clientID = clientID; 223 } 224 225 226 /** 227 * Sets the redirection URI. Corresponds to the optional 228 * {@code redirection_uri} parameter. 229 * 230 * @param redirectURI The redirection URI, {@code null} if not 231 * specified. 232 * 233 * @return This builder. 234 */ 235 public Builder redirectionURI(final URI redirectURI) { 236 237 this.redirectURI = redirectURI; 238 return this; 239 } 240 241 242 /** 243 * Sets the scope. Corresponds to the optional {@code scope} 244 * parameter. 245 * 246 * @param scope The scope, {@code null} if not specified. 247 * 248 * @return This builder. 249 */ 250 public Builder scope(final Scope scope) { 251 252 this.scope = scope; 253 return this; 254 } 255 256 257 /** 258 * Sets the state. Corresponds to the recommended {@code state} 259 * parameter. 260 * 261 * @param state The state, {@code null} if not specified. 262 * 263 * @return This builder. 264 */ 265 public Builder state(final State state) { 266 267 this.state = state; 268 return this; 269 } 270 271 272 /** 273 * Sets the response mode. Corresponds to the optional 274 * {@code response_mode} parameter. Use of this parameter is 275 * not recommended unless a non-default response mode is 276 * requested (e.g. form_post). 277 * 278 * @param rm The response mode, {@code null} if not specified. 279 * 280 * @return This builder. 281 */ 282 public Builder responseMode(final ResponseMode rm) { 283 284 this.rm = rm; 285 return this; 286 } 287 288 289 /** 290 * Sets the code challenge for Proof Key for Code Exchange 291 * (PKCE) by public OAuth clients. 292 * 293 * @param codeChallenge The code challenge, {@code null} 294 * if not specified. 295 * @param codeChallengeMethod The code challenge method, 296 * {@code null} if not specified. 297 * 298 * @return This builder. 299 */ 300 public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) { 301 302 this.codeChallenge = codeChallenge; 303 this.codeChallengeMethod = codeChallengeMethod; 304 return this; 305 } 306 307 308 /** 309 * Sets the specified additional custom parameter. 310 * 311 * @param name The parameter name. Must not be {@code null}. 312 * @param value The parameter value, {@code null} if not 313 * specified. 314 * 315 * @return This builder. 316 */ 317 public Builder customParameter(final String name, final String value) { 318 319 customParams.put(name, value); 320 return this; 321 } 322 323 324 /** 325 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 326 * request is intended. 327 * 328 * @param uri The endpoint URI, {@code null} if not specified. 329 * 330 * @return This builder. 331 */ 332 public Builder endpointURI(final URI uri) { 333 334 this.uri = uri; 335 return this; 336 } 337 338 339 /** 340 * Builds a new authorisation request. 341 * 342 * @return The authorisation request. 343 */ 344 public AuthorizationRequest build() { 345 346 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 347 } 348 } 349 350 351 /** 352 * Creates a new minimal authorisation request. 353 * 354 * @param uri The URI of the authorisation endpoint. May be 355 * {@code null} if the {@link #toHTTPRequest} method 356 * will not be used. 357 * @param rt The response type. Corresponds to the 358 * {@code response_type} parameter. Must not be 359 * {@code null}. 360 * @param clientID The client identifier. Corresponds to the 361 * {@code client_id} parameter. Must not be 362 * {@code null}. 363 */ 364 public AuthorizationRequest(final URI uri, 365 final ResponseType rt, 366 final ClientID clientID) { 367 368 this(uri, rt, null, clientID, null, null, null, null, null); 369 } 370 371 372 /** 373 * Creates a new authorisation request. 374 * 375 * @param uri The URI of the authorisation endpoint. 376 * May be {@code null} if the 377 * {@link #toHTTPRequest} method will not be 378 * used. 379 * @param rt The response type. Corresponds to the 380 * {@code response_type} parameter. Must not 381 * be {@code null}. 382 * @param rm The response mode. Corresponds to the 383 * optional {@code response_mode} parameter. 384 * Use of this parameter is not recommended 385 * unless a non-default response mode is 386 * requested (e.g. form_post). 387 * @param clientID The client identifier. Corresponds to the 388 * {@code client_id} parameter. Must not be 389 * {@code null}. 390 * @param redirectURI The redirection URI. Corresponds to the 391 * optional {@code redirect_uri} parameter. 392 * {@code null} if not specified. 393 * @param scope The request scope. Corresponds to the 394 * optional {@code scope} parameter. 395 * {@code null} if not specified. 396 * @param state The state. Corresponds to the recommended 397 * {@code state} parameter. {@code null} if 398 * not specified. 399 */ 400 public AuthorizationRequest(final URI uri, 401 final ResponseType rt, 402 final ResponseMode rm, 403 final ClientID clientID, 404 final URI redirectURI, 405 final Scope scope, 406 final State state) { 407 408 this(uri, rt, rm, clientID, redirectURI, scope, state, null, null); 409 } 410 411 412 /** 413 * Creates a new authorisation request with PKCE support. 414 * 415 * @param uri The URI of the authorisation endpoint. 416 * May be {@code null} if the 417 * {@link #toHTTPRequest} method will not be 418 * used. 419 * @param rt The response type. Corresponds to the 420 * {@code response_type} parameter. Must not 421 * be {@code null}. 422 * @param rm The response mode. Corresponds to the 423 * optional {@code response_mode} parameter. 424 * Use of this parameter is not recommended 425 * unless a non-default response mode is 426 * requested (e.g. form_post). 427 * @param clientID The client identifier. Corresponds to the 428 * {@code client_id} parameter. Must not be 429 * {@code null}. 430 * @param redirectURI The redirection URI. Corresponds to the 431 * optional {@code redirect_uri} parameter. 432 * {@code null} if not specified. 433 * @param scope The request scope. Corresponds to the 434 * optional {@code scope} parameter. 435 * {@code null} if not specified. 436 * @param state The state. Corresponds to the recommended 437 * {@code state} parameter. {@code null} if 438 * not specified. 439 * @param codeChallenge The code challenge for PKCE, {@code null} 440 * if not specified. 441 * @param codeChallengeMethod The code challenge method for PKCE, 442 * {@code null} if not specified. 443 */ 444 public AuthorizationRequest(final URI uri, 445 final ResponseType rt, 446 final ResponseMode rm, 447 final ClientID clientID, 448 final URI redirectURI, 449 final Scope scope, 450 final State state, 451 final CodeChallenge codeChallenge, 452 final CodeChallengeMethod codeChallengeMethod) { 453 454 this(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, Collections.<String,String>emptyMap()); 455 } 456 457 458 /** 459 * Creates a new authorisation request with PKCE support and additional 460 * custom parameters. 461 * 462 * @param uri The URI of the authorisation endpoint. 463 * May be {@code null} if the 464 * {@link #toHTTPRequest} method will not be 465 * used. 466 * @param rt The response type. Corresponds to the 467 * {@code response_type} parameter. Must not 468 * be {@code null}. 469 * @param rm The response mode. Corresponds to the 470 * optional {@code response_mode} parameter. 471 * Use of this parameter is not recommended 472 * unless a non-default response mode is 473 * requested (e.g. form_post). 474 * @param clientID The client identifier. Corresponds to the 475 * {@code client_id} parameter. Must not be 476 * {@code null}. 477 * @param redirectURI The redirection URI. Corresponds to the 478 * optional {@code redirect_uri} parameter. 479 * {@code null} if not specified. 480 * @param scope The request scope. Corresponds to the 481 * optional {@code scope} parameter. 482 * {@code null} if not specified. 483 * @param state The state. Corresponds to the recommended 484 * {@code state} parameter. {@code null} if 485 * not specified. 486 * @param codeChallenge The code challenge for PKCE, {@code null} 487 * if not specified. 488 * @param codeChallengeMethod The code challenge method for PKCE, 489 * {@code null} if not specified. 490 * @param customParams Additional custom parameters, empty map 491 * or {@code null} if none. 492 */ 493 public AuthorizationRequest(final URI uri, 494 final ResponseType rt, 495 final ResponseMode rm, 496 final ClientID clientID, 497 final URI redirectURI, 498 final Scope scope, 499 final State state, 500 final CodeChallenge codeChallenge, 501 final CodeChallengeMethod codeChallengeMethod, 502 final Map<String,String> customParams) { 503 504 super(uri); 505 506 if (rt == null) 507 throw new IllegalArgumentException("The response type must not be null"); 508 509 this.rt = rt; 510 511 this.rm = rm; 512 513 514 if (clientID == null) 515 throw new IllegalArgumentException("The client ID must not be null"); 516 517 this.clientID = clientID; 518 519 520 this.redirectURI = redirectURI; 521 this.scope = scope; 522 this.state = state; 523 524 this.codeChallenge = codeChallenge; 525 this.codeChallengeMethod = codeChallengeMethod; 526 527 if (MapUtils.isNotEmpty(customParams)) { 528 this.customParams = Collections.unmodifiableMap(customParams); 529 } else { 530 this.customParams = Collections.emptyMap(); 531 } 532 } 533 534 535 /** 536 * Returns the registered (standard) OAuth 2.0 authorisation request 537 * parameter names. 538 * 539 * @return The registered OAuth 2.0 authorisation request parameter 540 * names, as a unmodifiable set. 541 */ 542 public static Set<String> getRegisteredParameterNames() { 543 544 return REGISTERED_PARAMETER_NAMES; 545 } 546 547 548 /** 549 * Gets the response type. Corresponds to the {@code response_type} 550 * parameter. 551 * 552 * @return The response type. 553 */ 554 public ResponseType getResponseType() { 555 556 return rt; 557 } 558 559 560 /** 561 * Gets the optional response mode. Corresponds to the optional 562 * {@code response_mode} parameter. 563 * 564 * @return The response mode, {@code null} if not specified. 565 */ 566 public ResponseMode getResponseMode() { 567 568 return rm; 569 } 570 571 572 /** 573 * Returns the implied response mode, determined by the optional 574 * {@code response_mode} parameter, and if that isn't specified, by 575 * the {@code response_type}. 576 * 577 * @return The implied response mode. 578 */ 579 public ResponseMode impliedResponseMode() { 580 581 if (rm != null) { 582 return rm; 583 } else if (rt.impliesImplicitFlow()) { 584 return ResponseMode.FRAGMENT; 585 } else { 586 return ResponseMode.QUERY; 587 } 588 } 589 590 591 /** 592 * Gets the client identifier. Corresponds to the {@code client_id} 593 * parameter. 594 * 595 * @return The client identifier. 596 */ 597 public ClientID getClientID() { 598 599 return clientID; 600 } 601 602 603 /** 604 * Gets the redirection URI. Corresponds to the optional 605 * {@code redirection_uri} parameter. 606 * 607 * @return The redirection URI, {@code null} if not specified. 608 */ 609 public URI getRedirectionURI() { 610 611 return redirectURI; 612 } 613 614 615 /** 616 * Gets the scope. Corresponds to the optional {@code scope} parameter. 617 * 618 * @return The scope, {@code null} if not specified. 619 */ 620 public Scope getScope() { 621 622 return scope; 623 } 624 625 626 /** 627 * Gets the state. Corresponds to the recommended {@code state} 628 * parameter. 629 * 630 * @return The state, {@code null} if not specified. 631 */ 632 public State getState() { 633 634 return state; 635 } 636 637 638 /** 639 * Returns the code challenge for PKCE. 640 * 641 * @return The code challenge, {@code null} if not specified. 642 */ 643 public CodeChallenge getCodeChallenge() { 644 645 return codeChallenge; 646 } 647 648 649 /** 650 * Returns the code challenge method for PKCE. 651 * 652 * @return The code challenge method, {@code null} if not specified. 653 */ 654 public CodeChallengeMethod getCodeChallengeMethod() { 655 656 return codeChallengeMethod; 657 } 658 659 660 /** 661 * Returns the additional custom parameters. 662 * 663 * @return The additional custom parameters as a unmodifiable map, 664 * empty map if none. 665 */ 666 public Map<String,String> getCustomParameters () { 667 668 return customParams; 669 } 670 671 672 /** 673 * Returns the specified custom parameter. 674 * 675 * @param name The parameter name. Must not be {@code null}. 676 * 677 * @return The parameter value, {@code null} if not specified. 678 */ 679 public String getCustomParameter(final String name) { 680 681 return customParams.get(name); 682 } 683 684 685 /** 686 * Returns the parameters for this authorisation request. 687 * 688 * <p>Example parameters: 689 * 690 * <pre> 691 * response_type = code 692 * client_id = s6BhdRkqt3 693 * state = xyz 694 * redirect_uri = https://client.example.com/cb 695 * </pre> 696 * 697 * @return The parameters. 698 */ 699 public Map<String,String> toParameters() { 700 701 Map <String,String> params = new LinkedHashMap<>(); 702 703 // Put custom params first, so they may be overwritten by std params 704 params.putAll(customParams); 705 706 params.put("response_type", rt.toString()); 707 params.put("client_id", clientID.getValue()); 708 709 if (rm != null) { 710 params.put("response_mode", rm.getValue()); 711 } 712 713 if (redirectURI != null) 714 params.put("redirect_uri", redirectURI.toString()); 715 716 if (scope != null) 717 params.put("scope", scope.toString()); 718 719 if (state != null) 720 params.put("state", state.getValue()); 721 722 if (codeChallenge != null) { 723 params.put("code_challenge", codeChallenge.getValue()); 724 725 if (codeChallengeMethod != null) { 726 params.put("code_challenge_method", codeChallengeMethod.getValue()); 727 } 728 } 729 730 return params; 731 } 732 733 734 /** 735 * Returns the URI query string for this authorisation request. 736 * 737 * <p>Note that the '?' character preceding the query string in an URI 738 * is not included in the returned string. 739 * 740 * <p>Example URI query string: 741 * 742 * <pre> 743 * response_type=code 744 * &client_id=s6BhdRkqt3 745 * &state=xyz 746 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 747 * </pre> 748 * 749 * @return The URI query string. 750 */ 751 public String toQueryString() { 752 753 return URLUtils.serializeParameters(toParameters()); 754 } 755 756 757 /** 758 * Returns the complete URI representation for this authorisation 759 * request, consisting of the {@link #getEndpointURI authorization 760 * endpoint URI} with the {@link #toQueryString query string} appended. 761 * 762 * <p>Example URI: 763 * 764 * <pre> 765 * https://server.example.com/authorize? 766 * response_type=code 767 * &client_id=s6BhdRkqt3 768 * &state=xyz 769 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 770 * </pre> 771 * 772 * @return The URI representation. 773 */ 774 public URI toURI() { 775 776 if (getEndpointURI() == null) 777 throw new SerializeException("The authorization endpoint URI is not specified"); 778 779 StringBuilder sb = new StringBuilder(getEndpointURI().toString()); 780 sb.append('?'); 781 sb.append(toQueryString()); 782 try { 783 return new URI(sb.toString()); 784 } catch (URISyntaxException e) { 785 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 786 } 787 } 788 789 790 /** 791 * Returns the matching HTTP request. 792 * 793 * @param method The HTTP request method which can be GET or POST. Must 794 * not be {@code null}. 795 * 796 * @return The HTTP request. 797 */ 798 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 799 800 if (getEndpointURI() == null) 801 throw new SerializeException("The endpoint URI is not specified"); 802 803 HTTPRequest httpRequest; 804 805 URL endpointURL; 806 807 try { 808 endpointURL = getEndpointURI().toURL(); 809 810 } catch (MalformedURLException e) { 811 812 throw new SerializeException(e.getMessage(), e); 813 } 814 815 if (method.equals(HTTPRequest.Method.GET)) { 816 817 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 818 819 } else if (method.equals(HTTPRequest.Method.POST)) { 820 821 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 822 823 } else { 824 825 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 826 } 827 828 httpRequest.setQuery(toQueryString()); 829 830 return httpRequest; 831 } 832 833 834 @Override 835 public HTTPRequest toHTTPRequest() { 836 837 return toHTTPRequest(HTTPRequest.Method.GET); 838 } 839 840 841 /** 842 * Parses an authorisation request from the specified parameters. 843 * 844 * <p>Example parameters: 845 * 846 * <pre> 847 * response_type = code 848 * client_id = s6BhdRkqt3 849 * state = xyz 850 * redirect_uri = https://client.example.com/cb 851 * </pre> 852 * 853 * @param params The parameters. Must not be {@code null}. 854 * 855 * @return The authorisation request. 856 * 857 * @throws ParseException If the parameters couldn't be parsed to an 858 * authorisation request. 859 */ 860 public static AuthorizationRequest parse(final Map<String,String> params) 861 throws ParseException { 862 863 return parse(null, params); 864 } 865 866 867 /** 868 * Parses an authorisation request from the specified parameters. 869 * 870 * <p>Example parameters: 871 * 872 * <pre> 873 * response_type = code 874 * client_id = s6BhdRkqt3 875 * state = xyz 876 * redirect_uri = https://client.example.com/cb 877 * </pre> 878 * 879 * @param uri The URI of the authorisation endpoint. May be 880 * {@code null} if the {@link #toHTTPRequest()} method 881 * will not be used. 882 * @param params The parameters. Must not be {@code null}. 883 * 884 * @return The authorisation request. 885 * 886 * @throws ParseException If the parameters couldn't be parsed to an 887 * authorisation request. 888 */ 889 public static AuthorizationRequest parse(final URI uri, final Map<String,String> params) 890 throws ParseException { 891 892 // Parse mandatory client ID first 893 String v = params.get("client_id"); 894 895 if (StringUtils.isBlank(v)) { 896 String msg = "Missing \"client_id\" parameter"; 897 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 898 } 899 900 ClientID clientID = new ClientID(v); 901 902 903 // Parse optional redirection URI second 904 v = params.get("redirect_uri"); 905 906 URI redirectURI = null; 907 908 if (StringUtils.isNotBlank(v)) { 909 910 try { 911 redirectURI = new URI(v); 912 913 } catch (URISyntaxException e) { 914 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 915 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 916 clientID, null, null, null, e); 917 } 918 } 919 920 921 // Parse optional state third 922 State state = State.parse(params.get("state")); 923 924 925 // Parse mandatory response type 926 v = params.get("response_type"); 927 928 ResponseType rt; 929 930 try { 931 rt = ResponseType.parse(v); 932 933 } catch (ParseException e) { 934 // Only cause 935 String msg = "Missing \"response_type\" parameter"; 936 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 937 clientID, redirectURI, null, state, e); 938 } 939 940 941 // Parse the optional response mode 942 v = params.get("response_mode"); 943 944 ResponseMode rm = null; 945 946 if (StringUtils.isNotBlank(v)) { 947 rm = new ResponseMode(v); 948 } 949 950 951 // Parse optional scope 952 v = params.get("scope"); 953 954 Scope scope = null; 955 956 if (StringUtils.isNotBlank(v)) 957 scope = Scope.parse(v); 958 959 960 // Parse optional code challenge and method for PKCE 961 CodeChallenge codeChallenge = null; 962 CodeChallengeMethod codeChallengeMethod = null; 963 964 v = params.get("code_challenge"); 965 966 if (StringUtils.isNotBlank(v)) 967 codeChallenge = new CodeChallenge(v); 968 969 if (codeChallenge != null) { 970 971 v = params.get("code_challenge_method"); 972 973 if (StringUtils.isNotBlank(v)) 974 codeChallengeMethod = CodeChallengeMethod.parse(v); 975 } 976 977 // Parse additional custom parameters 978 Map<String,String> customParams = null; 979 980 for (Map.Entry<String,String> p: params.entrySet()) { 981 982 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 983 // We have a custom parameter 984 if (customParams == null) { 985 customParams = new HashMap<>(); 986 } 987 customParams.put(p.getKey(), p.getValue()); 988 } 989 } 990 991 992 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 993 } 994 995 996 /** 997 * Parses an authorisation request from the specified URI query string. 998 * 999 * <p>Example URI query string: 1000 * 1001 * <pre> 1002 * response_type=code 1003 * &client_id=s6BhdRkqt3 1004 * &state=xyz 1005 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1006 * </pre> 1007 * 1008 * @param query The URI query string. Must not be {@code null}. 1009 * 1010 * @return The authorisation request. 1011 * 1012 * @throws ParseException If the query string couldn't be parsed to an 1013 * authorisation request. 1014 */ 1015 public static AuthorizationRequest parse(final String query) 1016 throws ParseException { 1017 1018 return parse(null, URLUtils.parseParameters(query)); 1019 } 1020 1021 1022 /** 1023 * Parses an authorisation request from the specified URI query string. 1024 * 1025 * <p>Example URI query string: 1026 * 1027 * <pre> 1028 * response_type=code 1029 * &client_id=s6BhdRkqt3 1030 * &state=xyz 1031 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1032 * </pre> 1033 * 1034 * @param uri The URI of the authorisation endpoint. May be 1035 * {@code null} if the {@link #toHTTPRequest()} method 1036 * will not be used. 1037 * @param query The URI query string. Must not be {@code null}. 1038 * 1039 * @return The authorisation request. 1040 * 1041 * @throws ParseException If the query string couldn't be parsed to an 1042 * authorisation request. 1043 */ 1044 public static AuthorizationRequest parse(final URI uri, final String query) 1045 throws ParseException { 1046 1047 return parse(uri, URLUtils.parseParameters(query)); 1048 } 1049 1050 1051 /** 1052 * Parses an authorisation request from the specified URI. 1053 * 1054 * <p>Example URI: 1055 * 1056 * <pre> 1057 * https://server.example.com/authorize? 1058 * response_type=code 1059 * &client_id=s6BhdRkqt3 1060 * &state=xyz 1061 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1062 * </pre> 1063 * 1064 * @param uri The URI. Must not be {@code null}. 1065 * 1066 * @return The authorisation request. 1067 * 1068 * @throws ParseException If the URI couldn't be parsed to an 1069 * authorisation request. 1070 */ 1071 public static AuthorizationRequest parse(final URI uri) 1072 throws ParseException { 1073 1074 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1075 } 1076 1077 1078 /** 1079 * Parses an authorisation request from the specified HTTP request. 1080 * 1081 * <p>Example HTTP request (GET): 1082 * 1083 * <pre> 1084 * https://server.example.com/authorize? 1085 * response_type=code 1086 * &client_id=s6BhdRkqt3 1087 * &state=xyz 1088 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1089 * </pre> 1090 * 1091 * @param httpRequest The HTTP request. Must not be {@code null}. 1092 * 1093 * @return The authorisation request. 1094 * 1095 * @throws ParseException If the HTTP request couldn't be parsed to an 1096 * authorisation request. 1097 */ 1098 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1099 throws ParseException { 1100 1101 String query = httpRequest.getQuery(); 1102 1103 if (query == null) 1104 throw new ParseException("Missing URI query string"); 1105 1106 try { 1107 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 1108 1109 } catch (URISyntaxException e) { 1110 1111 throw new ParseException(e.getMessage(), e); 1112 } 1113 } 1114}