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