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