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 net.jcip.annotations.Immutable; 028 029import com.nimbusds.jwt.JWT; 030import com.nimbusds.jwt.JWTClaimsSet; 031import com.nimbusds.jwt.JWTParser; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.id.ClientID; 034import com.nimbusds.oauth2.sdk.id.State; 035import com.nimbusds.oauth2.sdk.pkce.CodeChallenge; 036import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod; 037import com.nimbusds.oauth2.sdk.pkce.CodeVerifier; 038import com.nimbusds.oauth2.sdk.util.*; 039import com.nimbusds.openid.connect.sdk.Prompt; 040 041 042/** 043 * Authorisation request. Used to authenticate an end-user and request the 044 * end-user's consent to grant the client access to a protected resource. 045 * Supports custom request parameters. 046 * 047 * <p>Extending classes may define additional request parameters as well as 048 * enforce tighter requirements on the base parameters. 049 * 050 * <p>Example HTTP request: 051 * 052 * <pre> 053 * https://server.example.com/authorize? 054 * response_type=code 055 * &client_id=s6BhdRkqt3 056 * &state=xyz 057 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 058 * </pre> 059 * 060 * <p>Related specifications: 061 * 062 * <ul> 063 * <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1. 064 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 065 * <li>OAuth 2.0 Form Post Response Mode 1.0. 066 * <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636). 067 * <li>Resource Indicators for OAuth 2.0 (RFC 8707) 068 * <li>OAuth 2.0 Incremental Authorization 069 * (draft-ietf-oauth-incremental-authz-00) 070 * <li>The OAuth 2.0 Authorization Framework: JWT Secured Authorization 071 * Request (JAR) draft-ietf-oauth-jwsreq-21 072 * <li>Financial-grade API: JWT Secured Authorization Response Mode for 073 * OAuth 2.0 (JARM) 074 * </ul> 075 */ 076@Immutable 077public class AuthorizationRequest extends AbstractRequest { 078 079 080 /** 081 * The registered parameter names. 082 */ 083 private static final Set<String> REGISTERED_PARAMETER_NAMES; 084 085 086 static { 087 Set<String> p = new HashSet<>(); 088 089 p.add("response_type"); 090 p.add("client_id"); 091 p.add("redirect_uri"); 092 p.add("scope"); 093 p.add("state"); 094 p.add("response_mode"); 095 p.add("code_challenge"); 096 p.add("code_challenge_method"); 097 p.add("resource"); 098 p.add("include_granted_scopes"); 099 p.add("request_uri"); 100 p.add("request"); 101 p.add("prompt"); 102 103 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 104 } 105 106 107 /** 108 * The response type (required unless in JAR). 109 */ 110 private final ResponseType rt; 111 112 113 /** 114 * The client identifier (required). 115 */ 116 private final ClientID clientID; 117 118 119 /** 120 * The redirection URI where the response will be sent (optional). 121 */ 122 private final URI redirectURI; 123 124 125 /** 126 * The scope (optional). 127 */ 128 private final Scope scope; 129 130 131 /** 132 * The opaque value to maintain state between the request and the 133 * callback (recommended). 134 */ 135 private final State state; 136 137 138 /** 139 * The response mode (optional). 140 */ 141 private final ResponseMode rm; 142 143 144 /** 145 * The authorisation code challenge for PKCE (optional). 146 */ 147 private final CodeChallenge codeChallenge; 148 149 150 /** 151 * The authorisation code challenge method for PKCE (optional). 152 */ 153 private final CodeChallengeMethod codeChallengeMethod; 154 155 156 /** 157 * The resource URI(s) (optional). 158 */ 159 private final List<URI> resources; 160 161 162 /** 163 * Indicates incremental authorisation (optional). 164 */ 165 private final boolean includeGrantedScopes; 166 167 168 /** 169 * Request object (optional). 170 */ 171 private final JWT requestObject; 172 173 174 /** 175 * Request object URI (optional). 176 */ 177 private final URI requestURI; 178 179 180 /** 181 * The requested prompt (optional). 182 */ 183 protected final Prompt prompt; 184 185 186 /** 187 * Custom parameters. 188 */ 189 private final Map<String,List<String>> customParams; 190 191 192 /** 193 * Builder for constructing authorisation requests. 194 */ 195 public static class Builder { 196 197 198 /** 199 * The endpoint URI (optional). 200 */ 201 private URI uri; 202 203 204 /** 205 * The response type (required unless in JAR). 206 */ 207 private ResponseType rt; 208 209 210 /** 211 * The client identifier (required). 212 */ 213 private final ClientID clientID; 214 215 216 /** 217 * The redirection URI where the response will be sent 218 * (optional). 219 */ 220 private URI redirectURI; 221 222 223 /** 224 * The scope (optional). 225 */ 226 private Scope scope; 227 228 229 /** 230 * The opaque value to maintain state between the request and 231 * the callback (recommended). 232 */ 233 private State state; 234 235 236 /** 237 * The response mode (optional). 238 */ 239 private ResponseMode rm; 240 241 242 /** 243 * The authorisation code challenge for PKCE (optional). 244 */ 245 private CodeChallenge codeChallenge; 246 247 248 /** 249 * The authorisation code challenge method for PKCE (optional). 250 */ 251 private CodeChallengeMethod codeChallengeMethod; 252 253 254 /** 255 * Indicates incremental authorisation (optional). 256 */ 257 private boolean includeGrantedScopes; 258 259 260 /** 261 * The resource URI(s) (optional). 262 */ 263 private List<URI> resources; 264 265 266 /** 267 * Request object (optional). 268 */ 269 private JWT requestObject; 270 271 272 /** 273 * Request object URI (optional). 274 */ 275 private URI requestURI; 276 277 278 /** 279 * The requested prompt (optional). 280 */ 281 private Prompt prompt; 282 283 284 /** 285 * Custom parameters. 286 */ 287 private final Map<String,List<String>> customParams = new HashMap<>(); 288 289 290 /** 291 * Creates a new authorisation request builder. 292 * 293 * @param rt The response type. Corresponds to the 294 * {@code response_type} parameter. Must not be 295 * {@code null}. 296 * @param clientID The client identifier. Corresponds to the 297 * {@code client_id} parameter. Must not be 298 * {@code null}. 299 */ 300 public Builder(final ResponseType rt, final ClientID clientID) { 301 302 if (rt == null) 303 throw new IllegalArgumentException("The response type must not be null"); 304 305 this.rt = rt; 306 307 308 if (clientID == null) 309 throw new IllegalArgumentException("The client ID must not be null"); 310 311 this.clientID = clientID; 312 } 313 314 315 /** 316 * Creates a new JWT secured authorisation request (JAR) 317 * builder. 318 * 319 * @param requestObject The request object. Must not be 320 * {@code null}.' 321 * @param clientID The client ID. Must not be 322 * {@code null}. 323 */ 324 public Builder(final JWT requestObject, final ClientID clientID) { 325 326 if (requestObject == null) 327 throw new IllegalArgumentException("The request object must not be null"); 328 329 this.requestObject = requestObject; 330 331 if (clientID == null) 332 throw new IllegalArgumentException("The client ID must not be null"); 333 334 this.clientID = clientID; 335 } 336 337 338 /** 339 * Creates a new JWT secured authorisation request (JAR) 340 * builder. 341 * 342 * @param requestURI The request object URI. Must not be 343 * {@code null}. 344 * @param clientID The client ID. Must not be {@code null}. 345 */ 346 public Builder(final URI requestURI, final ClientID clientID) { 347 348 if (requestURI == null) 349 throw new IllegalArgumentException("The request URI must not be null"); 350 351 this.requestURI = requestURI; 352 353 if (clientID == null) 354 throw new IllegalArgumentException("The client ID must not be null"); 355 356 this.clientID = clientID; 357 } 358 359 360 /** 361 * Creates a new authorisation request builder from the 362 * specified request. 363 * 364 * @param request The authorisation request. Must not be 365 * {@code null}. 366 */ 367 public Builder(final AuthorizationRequest request) { 368 369 uri = request.getEndpointURI(); 370 scope = request.scope; 371 rt = request.getResponseType(); 372 clientID = request.getClientID(); 373 redirectURI = request.getRedirectionURI(); 374 state = request.getState(); 375 rm = request.getResponseMode(); 376 codeChallenge = request.getCodeChallenge(); 377 codeChallengeMethod = request.getCodeChallengeMethod(); 378 resources = request.getResources(); 379 includeGrantedScopes = request.includeGrantedScopes(); 380 requestObject = request.requestObject; 381 requestURI = request.requestURI; 382 prompt = request.prompt; 383 customParams.putAll(request.getCustomParameters()); 384 } 385 386 387 /** 388 * Sets the response type. Corresponds to the 389 * {@code response_type} parameter. 390 * 391 * @param rt The response type. Must not be {@code null}. 392 * 393 * @return This builder. 394 */ 395 public Builder responseType(final ResponseType rt) { 396 397 if (rt == null) 398 throw new IllegalArgumentException("The response type must not be null"); 399 400 this.rt = rt; 401 return this; 402 } 403 404 405 /** 406 * Sets the redirection URI. Corresponds to the optional 407 * {@code redirection_uri} parameter. 408 * 409 * @param redirectURI The redirection URI, {@code null} if not 410 * specified. 411 * 412 * @return This builder. 413 */ 414 public Builder redirectionURI(final URI redirectURI) { 415 416 this.redirectURI = redirectURI; 417 return this; 418 } 419 420 421 /** 422 * Sets the scope. Corresponds to the optional {@code scope} 423 * parameter. 424 * 425 * @param scope The scope, {@code null} if not specified. 426 * 427 * @return This builder. 428 */ 429 public Builder scope(final Scope scope) { 430 431 this.scope = scope; 432 return this; 433 } 434 435 436 /** 437 * Sets the state. Corresponds to the recommended {@code state} 438 * parameter. 439 * 440 * @param state The state, {@code null} if not specified. 441 * 442 * @return This builder. 443 */ 444 public Builder state(final State state) { 445 446 this.state = state; 447 return this; 448 } 449 450 451 /** 452 * Sets the response mode. Corresponds to the optional 453 * {@code response_mode} parameter. Use of this parameter is 454 * not recommended unless a non-default response mode is 455 * requested (e.g. form_post). 456 * 457 * @param rm The response mode, {@code null} if not specified. 458 * 459 * @return This builder. 460 */ 461 public Builder responseMode(final ResponseMode rm) { 462 463 this.rm = rm; 464 return this; 465 } 466 467 468 /** 469 * Sets the code challenge for Proof Key for Code Exchange 470 * (PKCE) by public OAuth clients. 471 * 472 * @param codeChallenge The code challenge, {@code null} 473 * if not specified. 474 * @param codeChallengeMethod The code challenge method, 475 * {@code null} if not specified. 476 * 477 * @return This builder. 478 */ 479 @Deprecated 480 public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) { 481 482 this.codeChallenge = codeChallenge; 483 this.codeChallengeMethod = codeChallengeMethod; 484 return this; 485 } 486 487 488 /** 489 * Sets the code challenge for Proof Key for Code Exchange 490 * (PKCE) by public OAuth clients. 491 * 492 * @param codeVerifier The code verifier to use to 493 * compute the code challenge, 494 * {@code null} if PKCE is not 495 * specified. 496 * @param codeChallengeMethod The code challenge method, 497 * {@code null} if not specified. 498 * Defaults to 499 * {@link CodeChallengeMethod#PLAIN} 500 * if a code verifier is specified. 501 * 502 * @return This builder. 503 */ 504 public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) { 505 506 if (codeVerifier != null) { 507 CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault(); 508 this.codeChallenge = CodeChallenge.compute(method, codeVerifier); 509 this.codeChallengeMethod = method; 510 } else { 511 this.codeChallenge = null; 512 this.codeChallengeMethod = null; 513 } 514 return this; 515 } 516 517 518 /** 519 * Sets the resource server URI(s). 520 * 521 * @param resources The resource URI(s), {@code null} if not 522 * specified. 523 * 524 * @return This builder. 525 */ 526 public Builder resources(final URI ... resources) { 527 if (resources != null) { 528 this.resources = Arrays.asList(resources); 529 } else { 530 this.resources = null; 531 } 532 return this; 533 } 534 535 536 /** 537 * Requests incremental authorisation. 538 * 539 * @param includeGrantedScopes {@code true} to request 540 * incremental authorisation. 541 * 542 * @return This builder. 543 */ 544 public Builder includeGrantedScopes(final boolean includeGrantedScopes) { 545 546 this.includeGrantedScopes = includeGrantedScopes; 547 return this; 548 } 549 550 551 /** 552 * Sets the request object. Corresponds to the optional 553 * {@code request} parameter. Must not be specified together 554 * with a request object URI. 555 * 556 * @param requestObject The request object, {@code null} if not 557 * specified. 558 * 559 * @return This builder. 560 */ 561 public Builder requestObject(final JWT requestObject) { 562 563 this.requestObject = requestObject; 564 return this; 565 } 566 567 568 /** 569 * Sets the request object URI. Corresponds to the optional 570 * {@code request_uri} parameter. Must not be specified 571 * together with a request object. 572 * 573 * @param requestURI The request object URI, {@code null} if 574 * not specified. 575 * 576 * @return This builder. 577 */ 578 public Builder requestURI(final URI requestURI) { 579 580 this.requestURI = requestURI; 581 return this; 582 } 583 584 585 /** 586 * Sets the requested prompt. Corresponds to the optional 587 * {@code prompt} parameter. 588 * 589 * @param prompt The requested prompt, {@code null} if not 590 * specified. 591 * 592 * @return This builder. 593 */ 594 public Builder prompt(final Prompt prompt) { 595 596 this.prompt = prompt; 597 return this; 598 } 599 600 601 /** 602 * Sets a custom parameter. 603 * 604 * @param name The parameter name. Must not be {@code null}. 605 * @param values The parameter values, {@code null} if not 606 * specified. 607 * 608 * @return This builder. 609 */ 610 public Builder customParameter(final String name, final String ... values) { 611 612 if (values == null || values.length == 0) { 613 customParams.remove(name); 614 } else { 615 customParams.put(name, Arrays.asList(values)); 616 } 617 618 return this; 619 } 620 621 622 /** 623 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 624 * request is intended. 625 * 626 * @param uri The endpoint URI, {@code null} if not specified. 627 * 628 * @return This builder. 629 */ 630 public Builder endpointURI(final URI uri) { 631 632 this.uri = uri; 633 return this; 634 } 635 636 637 /** 638 * Builds a new authorisation request. 639 * 640 * @return The authorisation request. 641 */ 642 public AuthorizationRequest build() { 643 644 try { 645 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, 646 codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, 647 requestObject, requestURI, 648 prompt, 649 customParams); 650 } catch (IllegalArgumentException e) { 651 throw new IllegalStateException(e.getMessage(), e); 652 } 653 } 654 } 655 656 657 /** 658 * Creates a new minimal authorisation request. 659 * 660 * @param uri The URI of the authorisation endpoint. May be 661 * {@code null} if the {@link #toHTTPRequest} method 662 * will not be used. 663 * @param rt The response type. Corresponds to the 664 * {@code response_type} parameter. Must not be 665 * {@code null}. 666 * @param clientID The client identifier. Corresponds to the 667 * {@code client_id} parameter. Must not be 668 * {@code null}. 669 */ 670 public AuthorizationRequest(final URI uri, 671 final ResponseType rt, 672 final ClientID clientID) { 673 674 this(uri, rt, null, clientID, null, null, null, null, null, null, false, null, null, null, null); 675 } 676 677 678 /** 679 * Creates a new authorisation request. 680 * 681 * @param uri The URI of the authorisation endpoint. 682 * May be {@code null} if the 683 * {@link #toHTTPRequest} method will not be 684 * used. 685 * @param rt The response type. Corresponds to the 686 * {@code response_type} parameter. Must not 687 * be {@code null}. 688 * @param rm The response mode. Corresponds to the 689 * optional {@code response_mode} parameter. 690 * Use of this parameter is not recommended 691 * unless a non-default response mode is 692 * requested (e.g. form_post). 693 * @param clientID The client identifier. Corresponds to the 694 * {@code client_id} parameter. Must not be 695 * {@code null}. 696 * @param redirectURI The redirection URI. Corresponds to the 697 * optional {@code redirect_uri} parameter. 698 * {@code null} if not specified. 699 * @param scope The request scope. Corresponds to the 700 * optional {@code scope} parameter. 701 * {@code null} if not specified. 702 * @param state The state. Corresponds to the recommended 703 * {@code state} parameter. {@code null} if 704 * not specified. 705 */ 706 public AuthorizationRequest(final URI uri, 707 final ResponseType rt, 708 final ResponseMode rm, 709 final ClientID clientID, 710 final URI redirectURI, 711 final Scope scope, 712 final State state) { 713 714 this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null, null, null, null); 715 } 716 717 718 /** 719 * Creates a new authorisation request with extension and custom 720 * parameters. 721 * 722 * @param uri The URI of the authorisation endpoint. 723 * May be {@code null} if the 724 * {@link #toHTTPRequest} method will not 725 * be used. 726 * @param rt The response type. Corresponds to the 727 * {@code response_type} parameter. Must 728 * not be {@code null}, unless a request a 729 * request object or URI is specified. 730 * @param rm The response mode. Corresponds to the 731 * optional {@code response_mode} 732 * parameter. Use of this parameter is not 733 * recommended unless a non-default 734 * response mode is requested (e.g. 735 * form_post). 736 * @param clientID The client identifier. Corresponds to 737 * the {@code client_id} parameter. Must 738 * not be {@code null}, unless a request 739 * object or URI is specified. 740 * @param redirectURI The redirection URI. Corresponds to the 741 * optional {@code redirect_uri} parameter. 742 * {@code null} if not specified. 743 * @param scope The request scope. Corresponds to the 744 * optional {@code scope} parameter. 745 * {@code null} if not specified. 746 * @param state The state. Corresponds to the 747 * recommended {@code state} parameter. 748 * {@code null} if not specified. 749 * @param codeChallenge The code challenge for PKCE, 750 * {@code null} if not specified. 751 * @param codeChallengeMethod The code challenge method for PKCE, 752 * {@code null} if not specified. 753 * @param resources The resource URI(s), {@code null} if not 754 * specified. 755 * @param includeGrantedScopes {@code true} to request incremental 756 * authorisation. 757 * @param requestObject The request object. Corresponds to the 758 * optional {@code request} parameter. Must 759 * not be specified together with a request 760 * object URI. {@code null} if not 761 * specified. 762 * @param requestURI The request object URI. Corresponds to 763 * the optional {@code request_uri} 764 * parameter. Must not be specified 765 * together with a request object. 766 * {@code null} if not specified. 767 * @param prompt The requested prompt. Corresponds to the 768 * optional {@code prompt} parameter. 769 * @param customParams Custom parameters, empty map or 770 * {@code null} if none. 771 */ 772 public AuthorizationRequest(final URI uri, 773 final ResponseType rt, 774 final ResponseMode rm, 775 final ClientID clientID, 776 final URI redirectURI, 777 final Scope scope, 778 final State state, 779 final CodeChallenge codeChallenge, 780 final CodeChallengeMethod codeChallengeMethod, 781 final List<URI> resources, 782 final boolean includeGrantedScopes, 783 final JWT requestObject, 784 final URI requestURI, 785 final Prompt prompt, 786 final Map<String, List<String>> customParams) { 787 788 super(uri); 789 790 if (rt == null && requestObject == null && requestURI == null) 791 throw new IllegalArgumentException("The response type must not be null"); 792 793 this.rt = rt; 794 795 this.rm = rm; 796 797 798 if (clientID == null) 799 throw new IllegalArgumentException("The client ID must not be null"); 800 801 this.clientID = clientID; 802 803 804 this.redirectURI = redirectURI; 805 this.scope = scope; 806 this.state = state; 807 808 this.codeChallenge = codeChallenge; 809 this.codeChallengeMethod = codeChallengeMethod; 810 811 if (resources != null) { 812 for (URI resourceURI: resources) { 813 if (! ResourceUtils.isValidResourceURI(resourceURI)) 814 throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI); 815 } 816 } 817 818 this.resources = resources; 819 820 this.includeGrantedScopes = includeGrantedScopes; 821 822 if (requestObject != null && requestURI != null) 823 throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both"); 824 825 this.requestObject = requestObject; 826 this.requestURI = requestURI; 827 828 this.prompt = prompt; // technically OpenID 829 830 if (MapUtils.isNotEmpty(customParams)) { 831 this.customParams = Collections.unmodifiableMap(customParams); 832 } else { 833 this.customParams = Collections.emptyMap(); 834 } 835 } 836 837 838 /** 839 * Returns the registered (standard) OAuth 2.0 authorisation request 840 * parameter names. 841 * 842 * @return The registered OAuth 2.0 authorisation request parameter 843 * names, as a unmodifiable set. 844 */ 845 public static Set<String> getRegisteredParameterNames() { 846 847 return REGISTERED_PARAMETER_NAMES; 848 } 849 850 851 /** 852 * Gets the response type. Corresponds to the {@code response_type} 853 * parameter. 854 * 855 * @return The response type, may be {@code null} for a 856 * {@link #specifiesRequestObject() JWT secured authorisation 857 * request} with a {@link #getRequestObject() request} or 858 * {@link #getRequestURI() request_uri} parameter. 859 */ 860 public ResponseType getResponseType() { 861 862 return rt; 863 } 864 865 866 /** 867 * Gets the optional response mode. Corresponds to the optional 868 * {@code response_mode} parameter. 869 * 870 * @return The response mode, {@code null} if not specified. 871 */ 872 public ResponseMode getResponseMode() { 873 874 return rm; 875 } 876 877 878 /** 879 * Returns the implied response mode, determined by the optional 880 * {@code response_mode} parameter, and if that isn't specified, by 881 * the {@code response_type}. 882 * 883 * @return The implied response mode. 884 */ 885 public ResponseMode impliedResponseMode() { 886 887 return ResponseMode.resolve(rm, rt); 888 } 889 890 891 /** 892 * Gets the client identifier. Corresponds to the {@code client_id} 893 * parameter. 894 * 895 * @return The client identifier. 896 */ 897 public ClientID getClientID() { 898 899 return clientID; 900 } 901 902 903 /** 904 * Gets the redirection URI. Corresponds to the optional 905 * {@code redirection_uri} parameter. 906 * 907 * @return The redirection URI, {@code null} if not specified. 908 */ 909 public URI getRedirectionURI() { 910 911 return redirectURI; 912 } 913 914 915 /** 916 * Gets the scope. Corresponds to the optional {@code scope} parameter. 917 * 918 * @return The scope, {@code null} if not specified. 919 */ 920 public Scope getScope() { 921 922 return scope; 923 } 924 925 926 /** 927 * Gets the state. Corresponds to the recommended {@code state} 928 * parameter. 929 * 930 * @return The state, {@code null} if not specified. 931 */ 932 public State getState() { 933 934 return state; 935 } 936 937 938 /** 939 * Returns the code challenge for PKCE. 940 * 941 * @return The code challenge, {@code null} if not specified. 942 */ 943 public CodeChallenge getCodeChallenge() { 944 945 return codeChallenge; 946 } 947 948 949 /** 950 * Returns the code challenge method for PKCE. 951 * 952 * @return The code challenge method, {@code null} if not specified. 953 */ 954 public CodeChallengeMethod getCodeChallengeMethod() { 955 956 return codeChallengeMethod; 957 } 958 959 960 /** 961 * Returns the resource server URI. 962 * 963 * @return The resource URI(s), {@code null} if not specified. 964 */ 965 public List<URI> getResources() { 966 967 return resources; 968 } 969 970 971 /** 972 * Returns {@code true} if incremental authorisation is requested. 973 * 974 * @return {@code true} if incremental authorisation is requested, 975 * else {@code false}. 976 */ 977 public boolean includeGrantedScopes() { 978 979 return includeGrantedScopes; 980 } 981 982 983 /** 984 * Gets the request object. Corresponds to the optional {@code request} 985 * parameter. 986 * 987 * @return The request object, {@code null} if not specified. 988 */ 989 public JWT getRequestObject() { 990 991 return requestObject; 992 } 993 994 995 /** 996 * Gets the request object URI. Corresponds to the optional 997 * {@code request_uri} parameter. 998 * 999 * @return The request object URI, {@code null} if not specified. 1000 */ 1001 public URI getRequestURI() { 1002 1003 return requestURI; 1004 } 1005 1006 1007 /** 1008 * Returns {@code true} if this is a JWT secured authentication 1009 * request. 1010 * 1011 * @return {@code true} if a request object via a {@code request} or 1012 * {@code request_uri} parameter is specified, else 1013 * {@code false}. 1014 */ 1015 public boolean specifiesRequestObject() { 1016 1017 return requestObject != null || requestURI != null; 1018 } 1019 1020 1021 /** 1022 * Gets the requested prompt. Corresponds to the optional 1023 * {@code prompt} parameter. 1024 * 1025 * @return The requested prompt, {@code null} if not specified. 1026 */ 1027 public Prompt getPrompt() { 1028 1029 return prompt; 1030 } 1031 1032 1033 /** 1034 * Returns the additional custom parameters. 1035 * 1036 * @return The additional custom parameters as a unmodifiable map, 1037 * empty map if none. 1038 */ 1039 public Map<String,List<String>> getCustomParameters () { 1040 1041 return customParams; 1042 } 1043 1044 1045 /** 1046 * Returns the specified custom parameter. 1047 * 1048 * @param name The parameter name. Must not be {@code null}. 1049 * 1050 * @return The parameter value(s), {@code null} if not specified. 1051 */ 1052 public List<String> getCustomParameter(final String name) { 1053 1054 return customParams.get(name); 1055 } 1056 1057 1058 /** 1059 * Returns the URI query parameters for this authorisation request. 1060 * Query parameters which are part of the authorisation endpoint are 1061 * not included. 1062 * 1063 * <p>Example parameters: 1064 * 1065 * <pre> 1066 * response_type = code 1067 * client_id = s6BhdRkqt3 1068 * state = xyz 1069 * redirect_uri = https://client.example.com/cb 1070 * </pre> 1071 * 1072 * @return The parameters. 1073 */ 1074 public Map<String,List<String>> toParameters() { 1075 1076 // Put custom params first, so they may be overwritten by std params 1077 Map<String, List<String>> params = new LinkedHashMap<>(customParams); 1078 1079 params.put("client_id", Collections.singletonList(clientID.getValue())); 1080 1081 if (rt != null) 1082 params.put("response_type", Collections.singletonList(rt.toString())); 1083 1084 if (rm != null) 1085 params.put("response_mode", Collections.singletonList(rm.getValue())); 1086 1087 if (redirectURI != null) 1088 params.put("redirect_uri", Collections.singletonList(redirectURI.toString())); 1089 1090 if (scope != null) 1091 params.put("scope", Collections.singletonList(scope.toString())); 1092 1093 if (state != null) 1094 params.put("state", Collections.singletonList(state.getValue())); 1095 1096 if (codeChallenge != null) { 1097 params.put("code_challenge", Collections.singletonList(codeChallenge.getValue())); 1098 1099 if (codeChallengeMethod != null) { 1100 params.put("code_challenge_method", Collections.singletonList(codeChallengeMethod.getValue())); 1101 } 1102 } 1103 1104 if (includeGrantedScopes) 1105 params.put("include_granted_scopes", Collections.singletonList("true")); 1106 1107 if (resources != null) { 1108 List<String> resourceValues = new LinkedList<>(); 1109 for (URI resourceURI: resources) { 1110 if (resourceURI != null) { 1111 resourceValues.add(resourceURI.toString()); 1112 } 1113 } 1114 params.put("resource", resourceValues); 1115 } 1116 1117 if (requestObject != null) { 1118 try { 1119 params.put("request", Collections.singletonList(requestObject.serialize())); 1120 1121 } catch (IllegalStateException e) { 1122 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 1123 } 1124 } 1125 1126 if (requestURI != null) 1127 params.put("request_uri", Collections.singletonList(requestURI.toString())); 1128 1129 if (prompt != null) 1130 params.put("prompt", Collections.singletonList(prompt.toString())); 1131 1132 return params; 1133 } 1134 1135 1136 /** 1137 * Returns the parameters for this authorisation request as a JSON Web 1138 * Token (JWT) claims set. Intended for creating a request object. 1139 * 1140 * @return The parameters as JWT claim set. 1141 */ 1142 public JWTClaimsSet toJWTClaimsSet() { 1143 1144 if (specifiesRequestObject()) { 1145 throw new IllegalStateException("Cannot create nested JWT secured authorization request"); 1146 } 1147 1148 return JWTClaimsSetUtils.toJWTClaimsSet(toParameters()); 1149 } 1150 1151 1152 /** 1153 * Returns the URI query string for this authorisation request. 1154 * 1155 * <p>Note that the '?' character preceding the query string in an URI 1156 * is not included in the returned string. 1157 * 1158 * <p>Example URI query string: 1159 * 1160 * <pre> 1161 * response_type=code 1162 * &client_id=s6BhdRkqt3 1163 * &state=xyz 1164 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1165 * </pre> 1166 * 1167 * @return The URI query string. 1168 */ 1169 public String toQueryString() { 1170 1171 Map<String, List<String>> params = new HashMap<>(); 1172 if (getEndpointURI() != null) { 1173 params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery())); 1174 } 1175 params.putAll(toParameters()); 1176 1177 return URLUtils.serializeParameters(params); 1178 } 1179 1180 1181 /** 1182 * Returns the complete URI representation for this authorisation 1183 * request, consisting of the {@link #getEndpointURI authorization 1184 * endpoint URI} with the {@link #toQueryString query string} appended. 1185 * 1186 * <p>Example URI: 1187 * 1188 * <pre> 1189 * https://server.example.com/authorize? 1190 * response_type=code 1191 * &client_id=s6BhdRkqt3 1192 * &state=xyz 1193 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1194 * </pre> 1195 * 1196 * @return The URI representation. 1197 */ 1198 public URI toURI() { 1199 1200 if (getEndpointURI() == null) 1201 throw new SerializeException("The authorization endpoint URI is not specified"); 1202 1203 StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString()); 1204 sb.append('?'); 1205 sb.append(toQueryString()); 1206 try { 1207 return new URI(sb.toString()); 1208 } catch (URISyntaxException e) { 1209 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 1210 } 1211 } 1212 1213 1214 /** 1215 * Returns the matching HTTP request. 1216 * 1217 * @param method The HTTP request method which can be GET or POST. Must 1218 * not be {@code null}. 1219 * 1220 * @return The HTTP request. 1221 */ 1222 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 1223 1224 if (getEndpointURI() == null) 1225 throw new SerializeException("The endpoint URI is not specified"); 1226 1227 HTTPRequest httpRequest; 1228 1229 URL endpointURL; 1230 1231 try { 1232 endpointURL = getEndpointURI().toURL(); 1233 1234 } catch (MalformedURLException e) { 1235 1236 throw new SerializeException(e.getMessage(), e); 1237 } 1238 1239 if (method.equals(HTTPRequest.Method.GET)) { 1240 1241 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 1242 1243 } else if (method.equals(HTTPRequest.Method.POST)) { 1244 1245 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 1246 1247 } else { 1248 1249 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 1250 } 1251 1252 httpRequest.setQuery(toQueryString()); 1253 1254 return httpRequest; 1255 } 1256 1257 1258 @Override 1259 public HTTPRequest toHTTPRequest() { 1260 1261 return toHTTPRequest(HTTPRequest.Method.GET); 1262 } 1263 1264 1265 /** 1266 * Parses an authorisation request from the specified URI query 1267 * parameters. 1268 * 1269 * <p>Example parameters: 1270 * 1271 * <pre> 1272 * response_type = code 1273 * client_id = s6BhdRkqt3 1274 * state = xyz 1275 * redirect_uri = https://client.example.com/cb 1276 * </pre> 1277 * 1278 * @param params The parameters. Must not be {@code null}. 1279 * 1280 * @return The authorisation request. 1281 * 1282 * @throws ParseException If the parameters couldn't be parsed to an 1283 * authorisation request. 1284 */ 1285 public static AuthorizationRequest parse(final Map<String,List<String>> params) 1286 throws ParseException { 1287 1288 return parse(null, params); 1289 } 1290 1291 1292 /** 1293 * Parses an authorisation request from the specified URI and query 1294 * parameters. 1295 * 1296 * <p>Example parameters: 1297 * 1298 * <pre> 1299 * response_type = code 1300 * client_id = s6BhdRkqt3 1301 * state = xyz 1302 * redirect_uri = https://client.example.com/cb 1303 * </pre> 1304 * 1305 * @param uri The URI of the authorisation endpoint. May be 1306 * {@code null} if the {@link #toHTTPRequest()} method 1307 * will not be used. 1308 * @param params The parameters. Must not be {@code null}. 1309 * 1310 * @return The authorisation request. 1311 * 1312 * @throws ParseException If the parameters couldn't be parsed to an 1313 * authorisation request. 1314 */ 1315 public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params) 1316 throws ParseException { 1317 1318 // Parse response_mode, response_type, client_id, redirect_uri and state first, 1319 // needed if parsing results in a error response 1320 final ClientID clientID; 1321 URI redirectURI = null; 1322 State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state")); 1323 ResponseMode rm = null; 1324 ResponseType rt = null; 1325 1326 // Optional response_mode 1327 String v = MultivaluedMapUtils.getFirstValue(params, "response_mode"); 1328 if (StringUtils.isNotBlank(v)) { 1329 rm = new ResponseMode(v); 1330 } 1331 1332 // Mandatory client_id 1333 v = MultivaluedMapUtils.getFirstValue(params, "client_id"); 1334 if (StringUtils.isBlank(v)) { 1335 // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1 1336 String msg = "Missing \"client_id\" parameter"; 1337 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1338 } 1339 clientID = new ClientID(v); 1340 1341 // Optional redirect_uri 1342 v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri"); 1343 if (StringUtils.isNotBlank(v)) { 1344 try { 1345 redirectURI = new URI(v); 1346 } catch (URISyntaxException e) { 1347 // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1 1348 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 1349 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1350 } 1351 } 1352 1353 // Mandatory response_type, unless in JAR 1354 v = MultivaluedMapUtils.getFirstValue(params, "response_type"); 1355 if (StringUtils.isNotBlank(v)) { 1356 try { 1357 rt = ResponseType.parse(v); 1358 } catch (ParseException e) { 1359 // Only cause 1360 String msg = "Invalid \"response_type\" parameter"; 1361 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1362 clientID, redirectURI, rm, state, e); 1363 } 1364 } 1365 1366 // Check for a JAR in request or request_uri parameters 1367 v = MultivaluedMapUtils.getFirstValue(params, "request_uri"); 1368 URI requestURI = null; 1369 if (StringUtils.isNotBlank(v)) { 1370 try { 1371 requestURI = new URI(v); 1372 } catch (URISyntaxException e) { 1373 String msg = "Invalid \"request_uri\" parameter: " + e.getMessage(); 1374 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1375 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1376 } 1377 } 1378 1379 v = MultivaluedMapUtils.getFirstValue(params, "request"); 1380 1381 JWT requestObject = null; 1382 1383 if (StringUtils.isNotBlank(v)) { 1384 1385 // request_object and request_uri must not be present at the same time 1386 if (requestURI != null) { 1387 String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters"; 1388 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1389 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null); 1390 } 1391 1392 try { 1393 requestObject = JWTParser.parse(v); 1394 1395 } catch (java.text.ParseException e) { 1396 String msg = "Invalid \"request_object\" parameter: " + e.getMessage(); 1397 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1398 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1399 } 1400 } 1401 1402 // Response type mandatory, unless in JAR 1403 if (rt == null && requestObject == null && requestURI == null) { 1404 String msg = "Missing \"response_type\" parameter"; 1405 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1406 clientID, redirectURI, ResponseMode.resolve(rm, null), state, null); 1407 } 1408 1409 1410 // Parse optional scope 1411 v = MultivaluedMapUtils.getFirstValue(params, "scope"); 1412 1413 Scope scope = null; 1414 1415 if (StringUtils.isNotBlank(v)) 1416 scope = Scope.parse(v); 1417 1418 1419 // Parse optional code challenge and method for PKCE 1420 CodeChallenge codeChallenge = null; 1421 CodeChallengeMethod codeChallengeMethod = null; 1422 1423 v = MultivaluedMapUtils.getFirstValue(params, "code_challenge"); 1424 1425 if (StringUtils.isNotBlank(v)) 1426 codeChallenge = CodeChallenge.parse(v); 1427 1428 if (codeChallenge != null) { 1429 1430 v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method"); 1431 1432 if (StringUtils.isNotBlank(v)) 1433 codeChallengeMethod = CodeChallengeMethod.parse(v); 1434 } 1435 1436 List<URI> resources = null; 1437 1438 List<String> vList = params.get("resource"); 1439 1440 if (vList != null) { 1441 1442 resources = new LinkedList<>(); 1443 1444 for (String uriValue: vList) { 1445 1446 if (uriValue == null) 1447 continue; 1448 1449 String errMsg = "Invalid \"resource\" parameter: Must be an absolute URI and with no query or fragment: " + uriValue; 1450 1451 URI resourceURI; 1452 try { 1453 resourceURI = new URI(uriValue); 1454 } catch (URISyntaxException e) { 1455 throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg), 1456 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1457 } 1458 1459 if (! ResourceUtils.isValidResourceURI(resourceURI)) { 1460 throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg), 1461 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null); 1462 } 1463 1464 resources.add(resourceURI); 1465 } 1466 } 1467 1468 boolean includeGrantedScopes = false; 1469 v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes"); 1470 if ("true".equals(v)) { 1471 includeGrantedScopes = true; 1472 } 1473 1474 Prompt prompt; 1475 try { 1476 prompt = Prompt.parse(MultivaluedMapUtils.getFirstValue(params, "prompt")); 1477 1478 } catch (ParseException e) { 1479 String msg = "Invalid \"prompt\" parameter: " + e.getMessage(); 1480 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1481 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1482 } 1483 1484 // Parse custom parameters 1485 Map<String,List<String>> customParams = null; 1486 1487 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1488 1489 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1490 // We have a custom parameter 1491 if (customParams == null) { 1492 customParams = new HashMap<>(); 1493 } 1494 customParams.put(p.getKey(), p.getValue()); 1495 } 1496 } 1497 1498 1499 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, 1500 codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, 1501 requestObject, requestURI, 1502 prompt, 1503 customParams); 1504 } 1505 1506 1507 /** 1508 * Parses an authorisation request from the specified URI query string. 1509 * 1510 * <p>Example URI query string: 1511 * 1512 * <pre> 1513 * response_type=code 1514 * &client_id=s6BhdRkqt3 1515 * &state=xyz 1516 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1517 * </pre> 1518 * 1519 * @param query The URI query string. Must not be {@code null}. 1520 * 1521 * @return The authorisation request. 1522 * 1523 * @throws ParseException If the query string couldn't be parsed to an 1524 * authorisation request. 1525 */ 1526 public static AuthorizationRequest parse(final String query) 1527 throws ParseException { 1528 1529 return parse(null, URLUtils.parseParameters(query)); 1530 } 1531 1532 1533 /** 1534 * Parses an authorisation request from the specified URI and query 1535 * string. 1536 * 1537 * <p>Example URI query string: 1538 * 1539 * <pre> 1540 * response_type=code 1541 * &client_id=s6BhdRkqt3 1542 * &state=xyz 1543 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1544 * </pre> 1545 * 1546 * @param uri The URI of the authorisation endpoint. May be 1547 * {@code null} if the {@link #toHTTPRequest()} method 1548 * will not be used. 1549 * @param query The URI query string. Must not be {@code null}. 1550 * 1551 * @return The authorisation request. 1552 * 1553 * @throws ParseException If the query string couldn't be parsed to an 1554 * authorisation request. 1555 */ 1556 public static AuthorizationRequest parse(final URI uri, final String query) 1557 throws ParseException { 1558 1559 return parse(uri, URLUtils.parseParameters(query)); 1560 } 1561 1562 1563 /** 1564 * Parses an authorisation request from the specified URI. 1565 * 1566 * <p>Example URI: 1567 * 1568 * <pre> 1569 * https://server.example.com/authorize? 1570 * response_type=code 1571 * &client_id=s6BhdRkqt3 1572 * &state=xyz 1573 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1574 * </pre> 1575 * 1576 * @param uri The URI. Must not be {@code null}. 1577 * 1578 * @return The authorisation request. 1579 * 1580 * @throws ParseException If the URI couldn't be parsed to an 1581 * authorisation request. 1582 */ 1583 public static AuthorizationRequest parse(final URI uri) 1584 throws ParseException { 1585 1586 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1587 } 1588 1589 1590 /** 1591 * Parses an authorisation request from the specified HTTP request. 1592 * 1593 * <p>Example HTTP request (GET): 1594 * 1595 * <pre> 1596 * https://server.example.com/authorize? 1597 * response_type=code 1598 * &client_id=s6BhdRkqt3 1599 * &state=xyz 1600 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1601 * </pre> 1602 * 1603 * @param httpRequest The HTTP request. Must not be {@code null}. 1604 * 1605 * @return The authorisation request. 1606 * 1607 * @throws ParseException If the HTTP request couldn't be parsed to an 1608 * authorisation request. 1609 */ 1610 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1611 throws ParseException { 1612 1613 String query = httpRequest.getQuery(); 1614 1615 if (query == null) 1616 throw new ParseException("Missing URI query string"); 1617 1618 try { 1619 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 1620 1621 } catch (URISyntaxException e) { 1622 1623 throw new ParseException(e.getMessage(), e); 1624 } 1625 } 1626}