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.MapUtils; 034import com.nimbusds.oauth2.sdk.util.StringUtils; 035import com.nimbusds.oauth2.sdk.util.URIUtils; 036import com.nimbusds.oauth2.sdk.util.URLUtils; 037import net.jcip.annotations.Immutable; 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 * Creates a new authorisation request builder from the 246 * specified request. 247 * 248 * @param request The authorisation request. Must not be 249 * {@code null}. 250 */ 251 public Builder(final AuthorizationRequest request) { 252 253 uri = request.getEndpointURI(); 254 scope = request.scope; 255 rt = request.getResponseType(); 256 clientID = request.getClientID(); 257 redirectURI = request.getRedirectionURI(); 258 state = request.getState(); 259 rm = request.getResponseMode(); 260 codeChallenge = request.getCodeChallenge(); 261 codeChallengeMethod = request.getCodeChallengeMethod(); 262 customParams.putAll(request.getCustomParameters()); 263 } 264 265 266 /** 267 * Sets the redirection URI. Corresponds to the optional 268 * {@code redirection_uri} parameter. 269 * 270 * @param redirectURI The redirection URI, {@code null} if not 271 * specified. 272 * 273 * @return This builder. 274 */ 275 public Builder redirectionURI(final URI redirectURI) { 276 277 this.redirectURI = redirectURI; 278 return this; 279 } 280 281 282 /** 283 * Sets the scope. Corresponds to the optional {@code scope} 284 * parameter. 285 * 286 * @param scope The scope, {@code null} if not specified. 287 * 288 * @return This builder. 289 */ 290 public Builder scope(final Scope scope) { 291 292 this.scope = scope; 293 return this; 294 } 295 296 297 /** 298 * Sets the state. Corresponds to the recommended {@code state} 299 * parameter. 300 * 301 * @param state The state, {@code null} if not specified. 302 * 303 * @return This builder. 304 */ 305 public Builder state(final State state) { 306 307 this.state = state; 308 return this; 309 } 310 311 312 /** 313 * Sets the response mode. Corresponds to the optional 314 * {@code response_mode} parameter. Use of this parameter is 315 * not recommended unless a non-default response mode is 316 * requested (e.g. form_post). 317 * 318 * @param rm The response mode, {@code null} if not specified. 319 * 320 * @return This builder. 321 */ 322 public Builder responseMode(final ResponseMode rm) { 323 324 this.rm = rm; 325 return this; 326 } 327 328 329 /** 330 * Sets the code challenge for Proof Key for Code Exchange 331 * (PKCE) by public OAuth clients. 332 * 333 * @param codeChallenge The code challenge, {@code null} 334 * if not specified. 335 * @param codeChallengeMethod The code challenge method, 336 * {@code null} if not specified. 337 * 338 * @return This builder. 339 */ 340 @Deprecated 341 public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) { 342 343 this.codeChallenge = codeChallenge; 344 this.codeChallengeMethod = codeChallengeMethod; 345 return this; 346 } 347 348 349 /** 350 * Sets the code challenge for Proof Key for Code Exchange 351 * (PKCE) by public OAuth clients. 352 * 353 * @param codeVerifier The code verifier to use to 354 * compute the code challenge, 355 * {@code null} if PKCE is not 356 * specified. 357 * @param codeChallengeMethod The code challenge method, 358 * {@code null} if not specified. 359 * Defaults to 360 * {@link CodeChallengeMethod#PLAIN} 361 * if a code verifier is specified. 362 * 363 * @return This builder. 364 */ 365 public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) { 366 367 if (codeVerifier != null) { 368 CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault(); 369 this.codeChallenge = CodeChallenge.compute(method, codeVerifier); 370 this.codeChallengeMethod = method; 371 } else { 372 this.codeChallenge = null; 373 this.codeChallengeMethod = null; 374 } 375 return this; 376 } 377 378 379 /** 380 * Sets the specified additional custom parameter. 381 * 382 * @param name The parameter name. Must not be {@code null}. 383 * @param value The parameter value, {@code null} if not 384 * specified. 385 * 386 * @return This builder. 387 */ 388 public Builder customParameter(final String name, final String value) { 389 390 customParams.put(name, value); 391 return this; 392 } 393 394 395 /** 396 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 397 * request is intended. 398 * 399 * @param uri The endpoint URI, {@code null} if not specified. 400 * 401 * @return This builder. 402 */ 403 public Builder endpointURI(final URI uri) { 404 405 this.uri = uri; 406 return this; 407 } 408 409 410 /** 411 * Builds a new authorisation request. 412 * 413 * @return The authorisation request. 414 */ 415 public AuthorizationRequest build() { 416 417 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 418 } 419 } 420 421 422 /** 423 * Creates a new minimal authorisation request. 424 * 425 * @param uri The URI of the authorisation endpoint. May be 426 * {@code null} if the {@link #toHTTPRequest} method 427 * will not be used. 428 * @param rt The response type. Corresponds to the 429 * {@code response_type} parameter. Must not be 430 * {@code null}. 431 * @param clientID The client identifier. Corresponds to the 432 * {@code client_id} parameter. Must not be 433 * {@code null}. 434 */ 435 public AuthorizationRequest(final URI uri, 436 final ResponseType rt, 437 final ClientID clientID) { 438 439 this(uri, rt, null, clientID, null, null, null, null, null); 440 } 441 442 443 /** 444 * Creates a new authorisation request. 445 * 446 * @param uri The URI of the authorisation endpoint. 447 * May be {@code null} if the 448 * {@link #toHTTPRequest} method will not be 449 * used. 450 * @param rt The response type. Corresponds to the 451 * {@code response_type} parameter. Must not 452 * be {@code null}. 453 * @param rm The response mode. Corresponds to the 454 * optional {@code response_mode} parameter. 455 * Use of this parameter is not recommended 456 * unless a non-default response mode is 457 * requested (e.g. form_post). 458 * @param clientID The client identifier. Corresponds to the 459 * {@code client_id} parameter. Must not be 460 * {@code null}. 461 * @param redirectURI The redirection URI. Corresponds to the 462 * optional {@code redirect_uri} parameter. 463 * {@code null} if not specified. 464 * @param scope The request scope. Corresponds to the 465 * optional {@code scope} parameter. 466 * {@code null} if not specified. 467 * @param state The state. Corresponds to the recommended 468 * {@code state} parameter. {@code null} if 469 * not specified. 470 */ 471 public AuthorizationRequest(final URI uri, 472 final ResponseType rt, 473 final ResponseMode rm, 474 final ClientID clientID, 475 final URI redirectURI, 476 final Scope scope, 477 final State state) { 478 479 this(uri, rt, rm, clientID, redirectURI, scope, state, null, null); 480 } 481 482 483 /** 484 * Creates a new authorisation request with PKCE support. 485 * 486 * @param uri The URI of the authorisation endpoint. 487 * May be {@code null} if the 488 * {@link #toHTTPRequest} method will not be 489 * used. 490 * @param rt The response type. Corresponds to the 491 * {@code response_type} parameter. Must not 492 * be {@code null}. 493 * @param rm The response mode. Corresponds to the 494 * optional {@code response_mode} parameter. 495 * Use of this parameter is not recommended 496 * unless a non-default response mode is 497 * requested (e.g. form_post). 498 * @param clientID The client identifier. Corresponds to the 499 * {@code client_id} parameter. Must not be 500 * {@code null}. 501 * @param redirectURI The redirection URI. Corresponds to the 502 * optional {@code redirect_uri} parameter. 503 * {@code null} if not specified. 504 * @param scope The request scope. Corresponds to the 505 * optional {@code scope} parameter. 506 * {@code null} if not specified. 507 * @param state The state. Corresponds to the recommended 508 * {@code state} parameter. {@code null} if 509 * not specified. 510 * @param codeChallenge The code challenge for PKCE, {@code null} 511 * if not specified. 512 * @param codeChallengeMethod The code challenge method for PKCE, 513 * {@code null} if not specified. 514 */ 515 public AuthorizationRequest(final URI uri, 516 final ResponseType rt, 517 final ResponseMode rm, 518 final ClientID clientID, 519 final URI redirectURI, 520 final Scope scope, 521 final State state, 522 final CodeChallenge codeChallenge, 523 final CodeChallengeMethod codeChallengeMethod) { 524 525 this(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, Collections.<String,String>emptyMap()); 526 } 527 528 529 /** 530 * Creates a new authorisation request with PKCE support and additional 531 * custom parameters. 532 * 533 * @param uri The URI of the authorisation endpoint. 534 * May be {@code null} if the 535 * {@link #toHTTPRequest} method will not be 536 * used. 537 * @param rt The response type. Corresponds to the 538 * {@code response_type} parameter. Must not 539 * be {@code null}. 540 * @param rm The response mode. Corresponds to the 541 * optional {@code response_mode} parameter. 542 * Use of this parameter is not recommended 543 * unless a non-default response mode is 544 * requested (e.g. form_post). 545 * @param clientID The client identifier. Corresponds to the 546 * {@code client_id} parameter. Must not be 547 * {@code null}. 548 * @param redirectURI The redirection URI. Corresponds to the 549 * optional {@code redirect_uri} parameter. 550 * {@code null} if not specified. 551 * @param scope The request scope. Corresponds to the 552 * optional {@code scope} parameter. 553 * {@code null} if not specified. 554 * @param state The state. Corresponds to the recommended 555 * {@code state} parameter. {@code null} if 556 * not specified. 557 * @param codeChallenge The code challenge for PKCE, {@code null} 558 * if not specified. 559 * @param codeChallengeMethod The code challenge method for PKCE, 560 * {@code null} if not specified. 561 * @param customParams Additional custom parameters, empty map 562 * or {@code null} if none. 563 */ 564 public AuthorizationRequest(final URI uri, 565 final ResponseType rt, 566 final ResponseMode rm, 567 final ClientID clientID, 568 final URI redirectURI, 569 final Scope scope, 570 final State state, 571 final CodeChallenge codeChallenge, 572 final CodeChallengeMethod codeChallengeMethod, 573 final Map<String,String> customParams) { 574 575 super(uri); 576 577 if (rt == null) 578 throw new IllegalArgumentException("The response type must not be null"); 579 580 this.rt = rt; 581 582 this.rm = rm; 583 584 585 if (clientID == null) 586 throw new IllegalArgumentException("The client ID must not be null"); 587 588 this.clientID = clientID; 589 590 591 this.redirectURI = redirectURI; 592 this.scope = scope; 593 this.state = state; 594 595 this.codeChallenge = codeChallenge; 596 this.codeChallengeMethod = codeChallengeMethod; 597 598 if (MapUtils.isNotEmpty(customParams)) { 599 this.customParams = Collections.unmodifiableMap(customParams); 600 } else { 601 this.customParams = Collections.emptyMap(); 602 } 603 } 604 605 606 /** 607 * Returns the registered (standard) OAuth 2.0 authorisation request 608 * parameter names. 609 * 610 * @return The registered OAuth 2.0 authorisation request parameter 611 * names, as a unmodifiable set. 612 */ 613 public static Set<String> getRegisteredParameterNames() { 614 615 return REGISTERED_PARAMETER_NAMES; 616 } 617 618 619 /** 620 * Gets the response type. Corresponds to the {@code response_type} 621 * parameter. 622 * 623 * @return The response type. 624 */ 625 public ResponseType getResponseType() { 626 627 return rt; 628 } 629 630 631 /** 632 * Gets the optional response mode. Corresponds to the optional 633 * {@code response_mode} parameter. 634 * 635 * @return The response mode, {@code null} if not specified. 636 */ 637 public ResponseMode getResponseMode() { 638 639 return rm; 640 } 641 642 643 /** 644 * Returns the implied response mode, determined by the optional 645 * {@code response_mode} parameter, and if that isn't specified, by 646 * the {@code response_type}. 647 * 648 * @return The implied response mode. 649 */ 650 public ResponseMode impliedResponseMode() { 651 652 if (rm != null) { 653 return rm; 654 } else if (rt.impliesImplicitFlow() || rt.impliesHybridFlow()) { 655 return ResponseMode.FRAGMENT; 656 } else { 657 return ResponseMode.QUERY; 658 } 659 } 660 661 662 /** 663 * Gets the client identifier. Corresponds to the {@code client_id} 664 * parameter. 665 * 666 * @return The client identifier. 667 */ 668 public ClientID getClientID() { 669 670 return clientID; 671 } 672 673 674 /** 675 * Gets the redirection URI. Corresponds to the optional 676 * {@code redirection_uri} parameter. 677 * 678 * @return The redirection URI, {@code null} if not specified. 679 */ 680 public URI getRedirectionURI() { 681 682 return redirectURI; 683 } 684 685 686 /** 687 * Gets the scope. Corresponds to the optional {@code scope} parameter. 688 * 689 * @return The scope, {@code null} if not specified. 690 */ 691 public Scope getScope() { 692 693 return scope; 694 } 695 696 697 /** 698 * Gets the state. Corresponds to the recommended {@code state} 699 * parameter. 700 * 701 * @return The state, {@code null} if not specified. 702 */ 703 public State getState() { 704 705 return state; 706 } 707 708 709 /** 710 * Returns the code challenge for PKCE. 711 * 712 * @return The code challenge, {@code null} if not specified. 713 */ 714 public CodeChallenge getCodeChallenge() { 715 716 return codeChallenge; 717 } 718 719 720 /** 721 * Returns the code challenge method for PKCE. 722 * 723 * @return The code challenge method, {@code null} if not specified. 724 */ 725 public CodeChallengeMethod getCodeChallengeMethod() { 726 727 return codeChallengeMethod; 728 } 729 730 731 /** 732 * Returns the additional custom parameters. 733 * 734 * @return The additional custom parameters as a unmodifiable map, 735 * empty map if none. 736 */ 737 public Map<String,String> getCustomParameters () { 738 739 return customParams; 740 } 741 742 743 /** 744 * Returns the specified custom parameter. 745 * 746 * @param name The parameter name. Must not be {@code null}. 747 * 748 * @return The parameter value, {@code null} if not specified. 749 */ 750 public String getCustomParameter(final String name) { 751 752 return customParams.get(name); 753 } 754 755 756 /** 757 * Returns the parameters for this authorisation request. Query 758 * parameters which are part of the authorisation endpoint are not 759 * included. 760 * 761 * <p>Example parameters: 762 * 763 * <pre> 764 * response_type = code 765 * client_id = s6BhdRkqt3 766 * state = xyz 767 * redirect_uri = https://client.example.com/cb 768 * </pre> 769 * 770 * @return The parameters. 771 */ 772 public Map<String,String> toParameters() { 773 774 Map <String,String> params = new LinkedHashMap<>(); 775 776 // Put custom params first, so they may be overwritten by std params 777 params.putAll(customParams); 778 779 params.put("response_type", rt.toString()); 780 params.put("client_id", clientID.getValue()); 781 782 if (rm != null) { 783 params.put("response_mode", rm.getValue()); 784 } 785 786 if (redirectURI != null) 787 params.put("redirect_uri", redirectURI.toString()); 788 789 if (scope != null) 790 params.put("scope", scope.toString()); 791 792 if (state != null) 793 params.put("state", state.getValue()); 794 795 if (codeChallenge != null) { 796 params.put("code_challenge", codeChallenge.getValue()); 797 798 if (codeChallengeMethod != null) { 799 params.put("code_challenge_method", codeChallengeMethod.getValue()); 800 } 801 } 802 803 return params; 804 } 805 806 807 /** 808 * Returns the URI query string for this authorisation request. 809 * 810 * <p>Note that the '?' character preceding the query string in an URI 811 * is not included in the returned string. 812 * 813 * <p>Example URI query string: 814 * 815 * <pre> 816 * response_type=code 817 * &client_id=s6BhdRkqt3 818 * &state=xyz 819 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 820 * </pre> 821 * 822 * @return The URI query string. 823 */ 824 public String toQueryString() { 825 826 Map<String, String> params = new HashMap<>(); 827 if (getEndpointURI() != null) { 828 params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery())); 829 } 830 params.putAll(toParameters()); 831 832 return URLUtils.serializeParameters(params); 833 } 834 835 836 /** 837 * Returns the complete URI representation for this authorisation 838 * request, consisting of the {@link #getEndpointURI authorization 839 * endpoint URI} with the {@link #toQueryString query string} appended. 840 * 841 * <p>Example URI: 842 * 843 * <pre> 844 * https://server.example.com/authorize? 845 * response_type=code 846 * &client_id=s6BhdRkqt3 847 * &state=xyz 848 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 849 * </pre> 850 * 851 * @return The URI representation. 852 */ 853 public URI toURI() { 854 855 if (getEndpointURI() == null) 856 throw new SerializeException("The authorization endpoint URI is not specified"); 857 858 StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString()); 859 sb.append('?'); 860 sb.append(toQueryString()); 861 try { 862 return new URI(sb.toString()); 863 } catch (URISyntaxException e) { 864 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 865 } 866 } 867 868 869 /** 870 * Returns the matching HTTP request. 871 * 872 * @param method The HTTP request method which can be GET or POST. Must 873 * not be {@code null}. 874 * 875 * @return The HTTP request. 876 */ 877 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 878 879 if (getEndpointURI() == null) 880 throw new SerializeException("The endpoint URI is not specified"); 881 882 HTTPRequest httpRequest; 883 884 URL endpointURL; 885 886 try { 887 endpointURL = getEndpointURI().toURL(); 888 889 } catch (MalformedURLException e) { 890 891 throw new SerializeException(e.getMessage(), e); 892 } 893 894 if (method.equals(HTTPRequest.Method.GET)) { 895 896 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 897 898 } else if (method.equals(HTTPRequest.Method.POST)) { 899 900 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 901 902 } else { 903 904 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 905 } 906 907 httpRequest.setQuery(toQueryString()); 908 909 return httpRequest; 910 } 911 912 913 @Override 914 public HTTPRequest toHTTPRequest() { 915 916 return toHTTPRequest(HTTPRequest.Method.GET); 917 } 918 919 920 /** 921 * Parses an authorisation request from the specified parameters. 922 * 923 * <p>Example parameters: 924 * 925 * <pre> 926 * response_type = code 927 * client_id = s6BhdRkqt3 928 * state = xyz 929 * redirect_uri = https://client.example.com/cb 930 * </pre> 931 * 932 * @param params The parameters. Must not be {@code null}. 933 * 934 * @return The authorisation request. 935 * 936 * @throws ParseException If the parameters couldn't be parsed to an 937 * authorisation request. 938 */ 939 public static AuthorizationRequest parse(final Map<String,String> params) 940 throws ParseException { 941 942 return parse(null, params); 943 } 944 945 946 /** 947 * Parses an authorisation request from the specified parameters. 948 * 949 * <p>Example parameters: 950 * 951 * <pre> 952 * response_type = code 953 * client_id = s6BhdRkqt3 954 * state = xyz 955 * redirect_uri = https://client.example.com/cb 956 * </pre> 957 * 958 * @param uri The URI of the authorisation endpoint. May be 959 * {@code null} if the {@link #toHTTPRequest()} method 960 * will not be used. 961 * @param params The parameters. Must not be {@code null}. 962 * 963 * @return The authorisation request. 964 * 965 * @throws ParseException If the parameters couldn't be parsed to an 966 * authorisation request. 967 */ 968 public static AuthorizationRequest parse(final URI uri, final Map<String,String> params) 969 throws ParseException { 970 971 // Parse mandatory client ID first 972 String v = params.get("client_id"); 973 974 if (StringUtils.isBlank(v)) { 975 String msg = "Missing \"client_id\" parameter"; 976 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 977 } 978 979 ClientID clientID = new ClientID(v); 980 981 982 // Parse optional redirection URI second 983 v = params.get("redirect_uri"); 984 985 URI redirectURI = null; 986 987 if (StringUtils.isNotBlank(v)) { 988 989 try { 990 redirectURI = new URI(v); 991 992 } catch (URISyntaxException e) { 993 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 994 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 995 clientID, null, null, null, e); 996 } 997 } 998 999 1000 // Parse optional state third 1001 State state = State.parse(params.get("state")); 1002 1003 1004 // Parse mandatory response type 1005 v = params.get("response_type"); 1006 1007 ResponseType rt; 1008 1009 try { 1010 rt = ResponseType.parse(v); 1011 1012 } catch (ParseException e) { 1013 // Only cause 1014 String msg = "Missing \"response_type\" parameter"; 1015 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1016 clientID, redirectURI, null, state, e); 1017 } 1018 1019 1020 // Parse the optional response mode 1021 v = params.get("response_mode"); 1022 1023 ResponseMode rm = null; 1024 1025 if (StringUtils.isNotBlank(v)) { 1026 rm = new ResponseMode(v); 1027 } 1028 1029 1030 // Parse optional scope 1031 v = params.get("scope"); 1032 1033 Scope scope = null; 1034 1035 if (StringUtils.isNotBlank(v)) 1036 scope = Scope.parse(v); 1037 1038 1039 // Parse optional code challenge and method for PKCE 1040 CodeChallenge codeChallenge = null; 1041 CodeChallengeMethod codeChallengeMethod = null; 1042 1043 v = params.get("code_challenge"); 1044 1045 if (StringUtils.isNotBlank(v)) 1046 codeChallenge = CodeChallenge.parse(v); 1047 1048 if (codeChallenge != null) { 1049 1050 v = params.get("code_challenge_method"); 1051 1052 if (StringUtils.isNotBlank(v)) 1053 codeChallengeMethod = CodeChallengeMethod.parse(v); 1054 } 1055 1056 // Parse additional custom parameters 1057 Map<String,String> customParams = null; 1058 1059 for (Map.Entry<String,String> p: params.entrySet()) { 1060 1061 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1062 // We have a custom parameter 1063 if (customParams == null) { 1064 customParams = new HashMap<>(); 1065 } 1066 customParams.put(p.getKey(), p.getValue()); 1067 } 1068 } 1069 1070 1071 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 1072 } 1073 1074 1075 /** 1076 * Parses an authorisation request from the specified URI query string. 1077 * 1078 * <p>Example URI query string: 1079 * 1080 * <pre> 1081 * response_type=code 1082 * &client_id=s6BhdRkqt3 1083 * &state=xyz 1084 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1085 * </pre> 1086 * 1087 * @param query The URI query string. Must not be {@code null}. 1088 * 1089 * @return The authorisation request. 1090 * 1091 * @throws ParseException If the query string couldn't be parsed to an 1092 * authorisation request. 1093 */ 1094 public static AuthorizationRequest parse(final String query) 1095 throws ParseException { 1096 1097 return parse(null, URLUtils.parseParameters(query)); 1098 } 1099 1100 1101 /** 1102 * Parses an authorisation request from the specified URI query string. 1103 * 1104 * <p>Example URI query string: 1105 * 1106 * <pre> 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 of the authorisation endpoint. May be 1114 * {@code null} if the {@link #toHTTPRequest()} method 1115 * will not be used. 1116 * @param query The URI query string. Must not be {@code null}. 1117 * 1118 * @return The authorisation request. 1119 * 1120 * @throws ParseException If the query string couldn't be parsed to an 1121 * authorisation request. 1122 */ 1123 public static AuthorizationRequest parse(final URI uri, final String query) 1124 throws ParseException { 1125 1126 return parse(uri, URLUtils.parseParameters(query)); 1127 } 1128 1129 1130 /** 1131 * Parses an authorisation request from the specified URI. 1132 * 1133 * <p>Example URI: 1134 * 1135 * <pre> 1136 * https://server.example.com/authorize? 1137 * response_type=code 1138 * &client_id=s6BhdRkqt3 1139 * &state=xyz 1140 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1141 * </pre> 1142 * 1143 * @param uri The URI. Must not be {@code null}. 1144 * 1145 * @return The authorisation request. 1146 * 1147 * @throws ParseException If the URI couldn't be parsed to an 1148 * authorisation request. 1149 */ 1150 public static AuthorizationRequest parse(final URI uri) 1151 throws ParseException { 1152 1153 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1154 } 1155 1156 1157 /** 1158 * Parses an authorisation request from the specified HTTP request. 1159 * 1160 * <p>Example HTTP request (GET): 1161 * 1162 * <pre> 1163 * https://server.example.com/authorize? 1164 * response_type=code 1165 * &client_id=s6BhdRkqt3 1166 * &state=xyz 1167 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1168 * </pre> 1169 * 1170 * @param httpRequest The HTTP request. Must not be {@code null}. 1171 * 1172 * @return The authorisation request. 1173 * 1174 * @throws ParseException If the HTTP request couldn't be parsed to an 1175 * authorisation request. 1176 */ 1177 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1178 throws ParseException { 1179 1180 String query = httpRequest.getQuery(); 1181 1182 if (query == null) 1183 throw new ParseException("Missing URI query string"); 1184 1185 try { 1186 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 1187 1188 } catch (URISyntaxException e) { 1189 1190 throw new ParseException(e.getMessage(), e); 1191 } 1192 } 1193}