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 * 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()) { 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. 758 * 759 * <p>Example parameters: 760 * 761 * <pre> 762 * response_type = code 763 * client_id = s6BhdRkqt3 764 * state = xyz 765 * redirect_uri = https://client.example.com/cb 766 * </pre> 767 * 768 * @return The parameters. 769 */ 770 public Map<String,String> toParameters() { 771 772 Map <String,String> params = new LinkedHashMap<>(); 773 774 // Put custom params first, so they may be overwritten by std params 775 params.putAll(customParams); 776 777 params.put("response_type", rt.toString()); 778 params.put("client_id", clientID.getValue()); 779 780 if (rm != null) { 781 params.put("response_mode", rm.getValue()); 782 } 783 784 if (redirectURI != null) 785 params.put("redirect_uri", redirectURI.toString()); 786 787 if (scope != null) 788 params.put("scope", scope.toString()); 789 790 if (state != null) 791 params.put("state", state.getValue()); 792 793 if (codeChallenge != null) { 794 params.put("code_challenge", codeChallenge.getValue()); 795 796 if (codeChallengeMethod != null) { 797 params.put("code_challenge_method", codeChallengeMethod.getValue()); 798 } 799 } 800 801 return params; 802 } 803 804 805 /** 806 * Returns the URI query string for this authorisation request. 807 * 808 * <p>Note that the '?' character preceding the query string in an URI 809 * is not included in the returned string. 810 * 811 * <p>Example URI query string: 812 * 813 * <pre> 814 * response_type=code 815 * &client_id=s6BhdRkqt3 816 * &state=xyz 817 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 818 * </pre> 819 * 820 * @return The URI query string. 821 */ 822 public String toQueryString() { 823 824 return URLUtils.serializeParameters(toParameters()); 825 } 826 827 828 /** 829 * Returns the complete URI representation for this authorisation 830 * request, consisting of the {@link #getEndpointURI authorization 831 * endpoint URI} with the {@link #toQueryString query string} appended. 832 * 833 * <p>Example URI: 834 * 835 * <pre> 836 * https://server.example.com/authorize? 837 * response_type=code 838 * &client_id=s6BhdRkqt3 839 * &state=xyz 840 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 841 * </pre> 842 * 843 * @return The URI representation. 844 */ 845 public URI toURI() { 846 847 if (getEndpointURI() == null) 848 throw new SerializeException("The authorization endpoint URI is not specified"); 849 850 StringBuilder sb = new StringBuilder(getEndpointURI().toString()); 851 sb.append('?'); 852 sb.append(toQueryString()); 853 try { 854 return new URI(sb.toString()); 855 } catch (URISyntaxException e) { 856 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 857 } 858 } 859 860 861 /** 862 * Returns the matching HTTP request. 863 * 864 * @param method The HTTP request method which can be GET or POST. Must 865 * not be {@code null}. 866 * 867 * @return The HTTP request. 868 */ 869 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 870 871 if (getEndpointURI() == null) 872 throw new SerializeException("The endpoint URI is not specified"); 873 874 HTTPRequest httpRequest; 875 876 URL endpointURL; 877 878 try { 879 endpointURL = getEndpointURI().toURL(); 880 881 } catch (MalformedURLException e) { 882 883 throw new SerializeException(e.getMessage(), e); 884 } 885 886 if (method.equals(HTTPRequest.Method.GET)) { 887 888 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 889 890 } else if (method.equals(HTTPRequest.Method.POST)) { 891 892 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 893 894 } else { 895 896 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 897 } 898 899 httpRequest.setQuery(toQueryString()); 900 901 return httpRequest; 902 } 903 904 905 @Override 906 public HTTPRequest toHTTPRequest() { 907 908 return toHTTPRequest(HTTPRequest.Method.GET); 909 } 910 911 912 /** 913 * Parses an authorisation request from the specified parameters. 914 * 915 * <p>Example parameters: 916 * 917 * <pre> 918 * response_type = code 919 * client_id = s6BhdRkqt3 920 * state = xyz 921 * redirect_uri = https://client.example.com/cb 922 * </pre> 923 * 924 * @param params The parameters. Must not be {@code null}. 925 * 926 * @return The authorisation request. 927 * 928 * @throws ParseException If the parameters couldn't be parsed to an 929 * authorisation request. 930 */ 931 public static AuthorizationRequest parse(final Map<String,String> params) 932 throws ParseException { 933 934 return parse(null, params); 935 } 936 937 938 /** 939 * Parses an authorisation request from the specified parameters. 940 * 941 * <p>Example parameters: 942 * 943 * <pre> 944 * response_type = code 945 * client_id = s6BhdRkqt3 946 * state = xyz 947 * redirect_uri = https://client.example.com/cb 948 * </pre> 949 * 950 * @param uri The URI of the authorisation endpoint. May be 951 * {@code null} if the {@link #toHTTPRequest()} method 952 * will not be used. 953 * @param params The parameters. Must not be {@code null}. 954 * 955 * @return The authorisation request. 956 * 957 * @throws ParseException If the parameters couldn't be parsed to an 958 * authorisation request. 959 */ 960 public static AuthorizationRequest parse(final URI uri, final Map<String,String> params) 961 throws ParseException { 962 963 // Parse mandatory client ID first 964 String v = params.get("client_id"); 965 966 if (StringUtils.isBlank(v)) { 967 String msg = "Missing \"client_id\" parameter"; 968 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 969 } 970 971 ClientID clientID = new ClientID(v); 972 973 974 // Parse optional redirection URI second 975 v = params.get("redirect_uri"); 976 977 URI redirectURI = null; 978 979 if (StringUtils.isNotBlank(v)) { 980 981 try { 982 redirectURI = new URI(v); 983 984 } catch (URISyntaxException e) { 985 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 986 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 987 clientID, null, null, null, e); 988 } 989 } 990 991 992 // Parse optional state third 993 State state = State.parse(params.get("state")); 994 995 996 // Parse mandatory response type 997 v = params.get("response_type"); 998 999 ResponseType rt; 1000 1001 try { 1002 rt = ResponseType.parse(v); 1003 1004 } catch (ParseException e) { 1005 // Only cause 1006 String msg = "Missing \"response_type\" parameter"; 1007 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1008 clientID, redirectURI, null, state, e); 1009 } 1010 1011 1012 // Parse the optional response mode 1013 v = params.get("response_mode"); 1014 1015 ResponseMode rm = null; 1016 1017 if (StringUtils.isNotBlank(v)) { 1018 rm = new ResponseMode(v); 1019 } 1020 1021 1022 // Parse optional scope 1023 v = params.get("scope"); 1024 1025 Scope scope = null; 1026 1027 if (StringUtils.isNotBlank(v)) 1028 scope = Scope.parse(v); 1029 1030 1031 // Parse optional code challenge and method for PKCE 1032 CodeChallenge codeChallenge = null; 1033 CodeChallengeMethod codeChallengeMethod = null; 1034 1035 v = params.get("code_challenge"); 1036 1037 if (StringUtils.isNotBlank(v)) 1038 codeChallenge = CodeChallenge.parse(v); 1039 1040 if (codeChallenge != null) { 1041 1042 v = params.get("code_challenge_method"); 1043 1044 if (StringUtils.isNotBlank(v)) 1045 codeChallengeMethod = CodeChallengeMethod.parse(v); 1046 } 1047 1048 // Parse additional custom parameters 1049 Map<String,String> customParams = null; 1050 1051 for (Map.Entry<String,String> p: params.entrySet()) { 1052 1053 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1054 // We have a custom parameter 1055 if (customParams == null) { 1056 customParams = new HashMap<>(); 1057 } 1058 customParams.put(p.getKey(), p.getValue()); 1059 } 1060 } 1061 1062 1063 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 1064 } 1065 1066 1067 /** 1068 * Parses an authorisation request from the specified URI query string. 1069 * 1070 * <p>Example URI query string: 1071 * 1072 * <pre> 1073 * response_type=code 1074 * &client_id=s6BhdRkqt3 1075 * &state=xyz 1076 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1077 * </pre> 1078 * 1079 * @param query The URI query string. Must not be {@code null}. 1080 * 1081 * @return The authorisation request. 1082 * 1083 * @throws ParseException If the query string couldn't be parsed to an 1084 * authorisation request. 1085 */ 1086 public static AuthorizationRequest parse(final String query) 1087 throws ParseException { 1088 1089 return parse(null, URLUtils.parseParameters(query)); 1090 } 1091 1092 1093 /** 1094 * Parses an authorisation request from the specified URI query string. 1095 * 1096 * <p>Example URI query string: 1097 * 1098 * <pre> 1099 * response_type=code 1100 * &client_id=s6BhdRkqt3 1101 * &state=xyz 1102 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1103 * </pre> 1104 * 1105 * @param uri The URI of the authorisation endpoint. May be 1106 * {@code null} if the {@link #toHTTPRequest()} method 1107 * will not be used. 1108 * @param query The URI query string. Must not be {@code null}. 1109 * 1110 * @return The authorisation request. 1111 * 1112 * @throws ParseException If the query string couldn't be parsed to an 1113 * authorisation request. 1114 */ 1115 public static AuthorizationRequest parse(final URI uri, final String query) 1116 throws ParseException { 1117 1118 return parse(uri, URLUtils.parseParameters(query)); 1119 } 1120 1121 1122 /** 1123 * Parses an authorisation request from the specified URI. 1124 * 1125 * <p>Example URI: 1126 * 1127 * <pre> 1128 * https://server.example.com/authorize? 1129 * response_type=code 1130 * &client_id=s6BhdRkqt3 1131 * &state=xyz 1132 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1133 * </pre> 1134 * 1135 * @param uri The URI. Must not be {@code null}. 1136 * 1137 * @return The authorisation request. 1138 * 1139 * @throws ParseException If the URI couldn't be parsed to an 1140 * authorisation request. 1141 */ 1142 public static AuthorizationRequest parse(final URI uri) 1143 throws ParseException { 1144 1145 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1146 } 1147 1148 1149 /** 1150 * Parses an authorisation request from the specified HTTP request. 1151 * 1152 * <p>Example HTTP request (GET): 1153 * 1154 * <pre> 1155 * https://server.example.com/authorize? 1156 * response_type=code 1157 * &client_id=s6BhdRkqt3 1158 * &state=xyz 1159 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1160 * </pre> 1161 * 1162 * @param httpRequest The HTTP request. Must not be {@code null}. 1163 * 1164 * @return The authorisation request. 1165 * 1166 * @throws ParseException If the HTTP request couldn't be parsed to an 1167 * authorisation request. 1168 */ 1169 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1170 throws ParseException { 1171 1172 String query = httpRequest.getQuery(); 1173 1174 if (query == null) 1175 throw new ParseException("Missing URI query string"); 1176 1177 try { 1178 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 1179 1180 } catch (URISyntaxException e) { 1181 1182 throw new ParseException(e.getMessage(), e); 1183 } 1184 } 1185}