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