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