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 DPoP JWK SHA-256 thumbprint. Corresponds to the 653 * optional {@code dpop_jkt} parameter. 654 * 655 * @param dpopJKT DPoP JWK SHA-256 thumbprint, {@code null} if 656 * not specified. 657 * 658 * @return This builder. 659 */ 660 public Builder dPoPJWKThumbprintConfirmation(final JWKThumbprintConfirmation dpopJKT) { 661 this.dpopJKT = dpopJKT; 662 return this; 663 } 664 665 666 /** 667 * Sets the OpenID Connect Federation 1.0 trust chain. 668 * Corresponds to the optional {@code trust_chain} parameter. 669 * 670 * @param trustChain The trust chain, {@code null} if not 671 * specified. 672 * 673 * @return This builder. 674 */ 675 public Builder trustChain(final TrustChain trustChain) { 676 this.trustChain = trustChain; 677 return this; 678 } 679 680 681 /** 682 * Sets a custom parameter. 683 * 684 * @param name The parameter name. Must not be {@code null}. 685 * @param values The parameter values, {@code null} if not 686 * specified. 687 * 688 * @return This builder. 689 */ 690 public Builder customParameter(final String name, final String ... values) { 691 if (values == null || values.length == 0) { 692 customParams.remove(name); 693 } else { 694 customParams.put(name, Arrays.asList(values)); 695 } 696 return this; 697 } 698 699 700 /** 701 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 702 * request is intended. 703 * 704 * @param uri The endpoint URI, {@code null} if not specified. 705 * 706 * @return This builder. 707 */ 708 public Builder endpointURI(final URI uri) { 709 this.uri = uri; 710 return this; 711 } 712 713 714 /** 715 * Builds a new authorisation request. 716 * 717 * @return The authorisation request. 718 */ 719 public AuthorizationRequest build() { 720 try { 721 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, 722 codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, 723 requestObject, requestURI, 724 prompt, dpopJKT, trustChain, 725 customParams); 726 } catch (IllegalArgumentException e) { 727 throw new IllegalStateException(e.getMessage(), e); 728 } 729 } 730 } 731 732 733 /** 734 * Creates a new minimal authorisation request. 735 * 736 * @param uri The URI of the authorisation endpoint. May be 737 * {@code null} if the {@link #toHTTPRequest} method 738 * will not be used. 739 * @param rt The response type. Corresponds to the 740 * {@code response_type} parameter. Must not be 741 * {@code null}. 742 * @param clientID The client identifier. Corresponds to the 743 * {@code client_id} parameter. Must not be 744 * {@code null}. 745 */ 746 public AuthorizationRequest(final URI uri, 747 final ResponseType rt, 748 final ClientID clientID) { 749 750 this(uri, rt, null, clientID, null, null, null, null, null, null, false, null, null, null, null); 751 } 752 753 754 /** 755 * Creates a new authorisation request. 756 * 757 * @param uri The URI of the authorisation endpoint. 758 * May be {@code null} if the 759 * {@link #toHTTPRequest} method will not be 760 * used. 761 * @param rt The response type. Corresponds to the 762 * {@code response_type} parameter. Must not 763 * be {@code null}. 764 * @param rm The response mode. Corresponds to the 765 * optional {@code response_mode} parameter. 766 * Use of this parameter is not recommended 767 * unless a non-default response mode is 768 * requested (e.g. form_post). 769 * @param clientID The client identifier. Corresponds to the 770 * {@code client_id} parameter. Must not be 771 * {@code null}. 772 * @param redirectURI The redirection URI. Corresponds to the 773 * optional {@code redirect_uri} parameter. 774 * {@code null} if not specified. 775 * @param scope The request scope. Corresponds to the 776 * optional {@code scope} parameter. 777 * {@code null} if not specified. 778 * @param state The state. Corresponds to the recommended 779 * {@code state} parameter. {@code null} if 780 * not specified. 781 */ 782 public AuthorizationRequest(final URI uri, 783 final ResponseType rt, 784 final ResponseMode rm, 785 final ClientID clientID, 786 final URI redirectURI, 787 final Scope scope, 788 final State state) { 789 790 this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null, null, null, null); 791 } 792 793 794 /** 795 * Creates a new authorisation request with extension and custom 796 * parameters. 797 * 798 * @param uri The URI of the authorisation endpoint. 799 * May be {@code null} if the 800 * {@link #toHTTPRequest} method will not 801 * be used. 802 * @param rt The response type. Corresponds to the 803 * {@code response_type} parameter. Must 804 * not be {@code null}, unless a request a 805 * request object or URI is specified. 806 * @param rm The response mode. Corresponds to the 807 * optional {@code response_mode} 808 * parameter. Use of this parameter is not 809 * recommended unless a non-default 810 * response mode is requested (e.g. 811 * form_post). 812 * @param clientID The client identifier. Corresponds to 813 * the {@code client_id} parameter. Must 814 * not be {@code null}, unless a request 815 * object or URI is specified. 816 * @param redirectURI The redirection URI. Corresponds to the 817 * optional {@code redirect_uri} parameter. 818 * {@code null} if not specified. 819 * @param scope The request scope. Corresponds to the 820 * optional {@code scope} parameter. 821 * {@code null} if not specified. 822 * @param state The state. Corresponds to the 823 * recommended {@code state} parameter. 824 * {@code null} if not specified. 825 * @param codeChallenge The code challenge for PKCE, 826 * {@code null} if not specified. 827 * @param codeChallengeMethod The code challenge method for PKCE, 828 * {@code null} if not specified. 829 * @param resources The resource URI(s), {@code null} if not 830 * specified. 831 * @param includeGrantedScopes {@code true} to request incremental 832 * authorisation. 833 * @param requestObject The request object. Corresponds to the 834 * optional {@code request} parameter. Must 835 * not be specified together with a request 836 * object URI. {@code null} if not 837 * specified. 838 * @param requestURI The request object URI. Corresponds to 839 * the optional {@code request_uri} 840 * parameter. Must not be specified 841 * together with a request object. 842 * {@code null} if not specified. 843 * @param prompt The requested prompt. Corresponds to the 844 * optional {@code prompt} parameter. 845 * @param customParams Custom parameters, empty map or 846 * {@code null} if none. 847 */ 848 @Deprecated 849 public AuthorizationRequest(final URI uri, 850 final ResponseType rt, 851 final ResponseMode rm, 852 final ClientID clientID, 853 final URI redirectURI, 854 final Scope scope, 855 final State state, 856 final CodeChallenge codeChallenge, 857 final CodeChallengeMethod codeChallengeMethod, 858 final List<URI> resources, 859 final boolean includeGrantedScopes, 860 final JWT requestObject, 861 final URI requestURI, 862 final Prompt prompt, 863 final Map<String, List<String>> customParams) { 864 865 this(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, requestObject, requestURI, prompt, null, customParams); 866 } 867 868 869 /** 870 * Creates a new authorisation request with extension and custom 871 * parameters. 872 * 873 * @param uri The URI of the authorisation endpoint. 874 * May be {@code null} if the 875 * {@link #toHTTPRequest} method will not 876 * be used. 877 * @param rt The response type. Corresponds to the 878 * {@code response_type} parameter. Must 879 * not be {@code null}, unless a request a 880 * request object or URI is specified. 881 * @param rm The response mode. Corresponds to the 882 * optional {@code response_mode} 883 * parameter. Use of this parameter is not 884 * recommended unless a non-default 885 * response mode is requested (e.g. 886 * form_post). 887 * @param clientID The client identifier. Corresponds to 888 * the {@code client_id} parameter. Must 889 * not be {@code null}, unless a request 890 * object or URI is specified. 891 * @param redirectURI The redirection URI. Corresponds to the 892 * optional {@code redirect_uri} parameter. 893 * {@code null} if not specified. 894 * @param scope The request scope. Corresponds to the 895 * optional {@code scope} parameter. 896 * {@code null} if not specified. 897 * @param state The state. Corresponds to the 898 * recommended {@code state} parameter. 899 * {@code null} if not specified. 900 * @param codeChallenge The code challenge for PKCE, 901 * {@code null} if not specified. 902 * @param codeChallengeMethod The code challenge method for PKCE, 903 * {@code null} if not specified. 904 * @param resources The resource URI(s), {@code null} if not 905 * specified. 906 * @param includeGrantedScopes {@code true} to request incremental 907 * authorisation. 908 * @param requestObject The request object. Corresponds to the 909 * optional {@code request} parameter. Must 910 * not be specified together with a request 911 * object URI. {@code null} if not 912 * specified. 913 * @param requestURI The request object URI. Corresponds to 914 * the optional {@code request_uri} 915 * parameter. Must not be specified 916 * together with a request object. 917 * {@code null} if not specified. 918 * @param prompt The requested prompt. Corresponds to the 919 * optional {@code prompt} parameter. 920 * @param dpopJKT The DPoP JWK SHA-256 thumbprint, 921 * {@code null} if not specified. 922 * @param customParams Custom parameters, empty map or 923 * {@code null} if none. 924 */ 925 @Deprecated 926 public AuthorizationRequest(final URI uri, 927 final ResponseType rt, 928 final ResponseMode rm, 929 final ClientID clientID, 930 final URI redirectURI, 931 final Scope scope, 932 final State state, 933 final CodeChallenge codeChallenge, 934 final CodeChallengeMethod codeChallengeMethod, 935 final List<URI> resources, 936 final boolean includeGrantedScopes, 937 final JWT requestObject, 938 final URI requestURI, 939 final Prompt prompt, 940 final JWKThumbprintConfirmation dpopJKT, 941 final Map<String, List<String>> customParams) { 942 943 this(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, requestObject, requestURI, prompt, dpopJKT, null, customParams); 944 } 945 946 947 /** 948 * Creates a new authorisation request with extension and custom 949 * parameters. 950 * 951 * @param uri The URI of the authorisation endpoint. 952 * May be {@code null} if the 953 * {@link #toHTTPRequest} method will not 954 * be used. 955 * @param rt The response type. Corresponds to the 956 * {@code response_type} parameter. Must 957 * not be {@code null}, unless a request a 958 * request object or URI is specified. 959 * @param rm The response mode. Corresponds to the 960 * optional {@code response_mode} 961 * parameter. Use of this parameter is not 962 * recommended unless a non-default 963 * response mode is requested (e.g. 964 * form_post). 965 * @param clientID The client identifier. Corresponds to 966 * the {@code client_id} parameter. Must 967 * not be {@code null}, unless a request 968 * object or URI is specified. 969 * @param redirectURI The redirection URI. Corresponds to the 970 * optional {@code redirect_uri} parameter. 971 * {@code null} if not specified. 972 * @param scope The request scope. Corresponds to the 973 * optional {@code scope} parameter. 974 * {@code null} if not specified. 975 * @param state The state. Corresponds to the 976 * recommended {@code state} parameter. 977 * {@code null} if not specified. 978 * @param codeChallenge The code challenge for PKCE, 979 * {@code null} if not specified. 980 * @param codeChallengeMethod The code challenge method for PKCE, 981 * {@code null} if not specified. 982 * @param resources The resource URI(s), {@code null} if not 983 * specified. 984 * @param includeGrantedScopes {@code true} to request incremental 985 * authorisation. 986 * @param requestObject The request object. Corresponds to the 987 * optional {@code request} parameter. Must 988 * not be specified together with a request 989 * object URI. {@code null} if not 990 * specified. 991 * @param requestURI The request object URI. Corresponds to 992 * the optional {@code request_uri} 993 * parameter. Must not be specified 994 * together with a request object. 995 * {@code null} if not specified. 996 * @param prompt The requested prompt. Corresponds to the 997 * optional {@code prompt} parameter. 998 * @param dpopJKT The DPoP JWK SHA-256 thumbprint, 999 * {@code null} if not specified. 1000 * @param trustChain The OpenID Connect Federation 1.0 trust 1001 * chain, {@code null} if not specified. 1002 * @param customParams Custom parameters, empty map or 1003 * {@code null} if none. 1004 */ 1005 public AuthorizationRequest(final URI uri, 1006 final ResponseType rt, 1007 final ResponseMode rm, 1008 final ClientID clientID, 1009 final URI redirectURI, 1010 final Scope scope, 1011 final State state, 1012 final CodeChallenge codeChallenge, 1013 final CodeChallengeMethod codeChallengeMethod, 1014 final List<URI> resources, 1015 final boolean includeGrantedScopes, 1016 final JWT requestObject, 1017 final URI requestURI, 1018 final Prompt prompt, 1019 final JWKThumbprintConfirmation dpopJKT, 1020 final TrustChain trustChain, 1021 final Map<String, List<String>> customParams) { 1022 1023 super(uri); 1024 1025 if (rt == null && requestObject == null && requestURI == null) 1026 throw new IllegalArgumentException("The response type must not be null"); 1027 1028 this.rt = rt; 1029 1030 this.rm = rm; 1031 1032 1033 if (clientID == null) 1034 throw new IllegalArgumentException("The client ID must not be null"); 1035 1036 this.clientID = clientID; 1037 1038 1039 this.redirectURI = redirectURI; 1040 this.scope = scope; 1041 this.state = state; 1042 1043 this.codeChallenge = codeChallenge; 1044 this.codeChallengeMethod = codeChallengeMethod; 1045 1046 this.resources = ResourceUtils.ensureLegalResourceURIs(resources); 1047 1048 this.includeGrantedScopes = includeGrantedScopes; 1049 1050 if (requestObject != null && requestURI != null) 1051 throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both"); 1052 1053 this.requestObject = requestObject; 1054 this.requestURI = requestURI; 1055 1056 if (requestObject instanceof SignedJWT) { 1057 // Make sure the "sub" claim is not set to the client_id value 1058 // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8 1059 JWTClaimsSet requestObjectClaims; 1060 try { 1061 requestObjectClaims = requestObject.getJWTClaimsSet(); 1062 } catch (java.text.ParseException e) { 1063 // Should never happen 1064 throw new IllegalArgumentException("Illegal request parameter: " + e.getMessage(), e); 1065 } 1066 if (clientID.getValue().equals(requestObjectClaims.getSubject())) { 1067 throw new IllegalArgumentException("Illegal request parameter: The JWT sub (subject) claim must not equal the client_id"); 1068 } 1069 } 1070 1071 this.prompt = prompt; // technically OpenID 1072 1073 this.dpopJKT = dpopJKT; 1074 1075 this.trustChain = trustChain; 1076 1077 if (MapUtils.isNotEmpty(customParams)) { 1078 this.customParams = Collections.unmodifiableMap(customParams); 1079 } else { 1080 this.customParams = Collections.emptyMap(); 1081 } 1082 } 1083 1084 1085 /** 1086 * Returns the registered (standard) OAuth 2.0 authorisation request 1087 * parameter names. 1088 * 1089 * @return The registered OAuth 2.0 authorisation request parameter 1090 * names, as a unmodifiable set. 1091 */ 1092 public static Set<String> getRegisteredParameterNames() { 1093 1094 return REGISTERED_PARAMETER_NAMES; 1095 } 1096 1097 1098 /** 1099 * Returns the response type. Corresponds to the {@code response_type} 1100 * parameter. 1101 * 1102 * @return The response type, may be {@code null} for a 1103 * {@link #specifiesRequestObject() JWT secured authorisation 1104 * request} with a {@link #getRequestObject() request} or 1105 * {@link #getRequestURI() request_uri} parameter. 1106 */ 1107 public ResponseType getResponseType() { 1108 return rt; 1109 } 1110 1111 1112 /** 1113 * Returns the optional response mode. Corresponds to the optional 1114 * {@code response_mode} parameter. 1115 * 1116 * @return The response mode, {@code null} if not specified. 1117 */ 1118 public ResponseMode getResponseMode() { 1119 return rm; 1120 } 1121 1122 1123 /** 1124 * Returns the implied response mode, determined by the optional 1125 * {@code response_mode} parameter, and if that isn't specified, by 1126 * the {@code response_type}. 1127 * 1128 * <p>If the {@link ResponseMode#JWT jwt} response mode shortcut from 1129 * JARM is explicitly requested expands it to 1130 * {@link ResponseMode#QUERY_JWT query.jwt} or 1131 * {@link ResponseMode#FRAGMENT_JWT fragment.jwt} depending on the 1132 * response type ({@code response_type}). 1133 * 1134 * @return The implied response mode. 1135 */ 1136 public ResponseMode impliedResponseMode() { 1137 return ResponseMode.resolve(rm, rt); 1138 } 1139 1140 1141 /** 1142 * Returns the client identifier. Corresponds to the {@code client_id} 1143 * parameter. 1144 * 1145 * @return The client identifier. 1146 */ 1147 public ClientID getClientID() { 1148 return clientID; 1149 } 1150 1151 1152 /** 1153 * Returns the redirection URI. Corresponds to the optional 1154 * {@code redirection_uri} parameter. 1155 * 1156 * @return The redirection URI, {@code null} if not specified. 1157 */ 1158 public URI getRedirectionURI() { 1159 return redirectURI; 1160 } 1161 1162 1163 /** 1164 * Returns the scope. Corresponds to the optional {@code scope} 1165 * parameter. 1166 * 1167 * @return The scope, {@code null} if not specified. 1168 */ 1169 public Scope getScope() { 1170 return scope; 1171 } 1172 1173 1174 /** 1175 * Returns the state. Corresponds to the recommended {@code state} 1176 * parameter. 1177 * 1178 * @return The state, {@code null} if not specified. 1179 */ 1180 public State getState() { 1181 return state; 1182 } 1183 1184 1185 /** 1186 * Returns the code challenge for PKCE. 1187 * 1188 * @return The code challenge, {@code null} if not specified. 1189 */ 1190 public CodeChallenge getCodeChallenge() { 1191 return codeChallenge; 1192 } 1193 1194 1195 /** 1196 * Returns the code challenge method for PKCE. 1197 * 1198 * @return The code challenge method, {@code null} if not specified. 1199 */ 1200 public CodeChallengeMethod getCodeChallengeMethod() { 1201 return codeChallengeMethod; 1202 } 1203 1204 1205 /** 1206 * Returns the resource server URI. 1207 * 1208 * @return The resource URI(s), {@code null} if not specified. 1209 */ 1210 public List<URI> getResources() { 1211 return resources; 1212 } 1213 1214 1215 /** 1216 * Returns {@code true} if incremental authorisation is requested. 1217 * 1218 * @return {@code true} if incremental authorisation is requested, 1219 * else {@code false}. 1220 */ 1221 public boolean includeGrantedScopes() { 1222 return includeGrantedScopes; 1223 } 1224 1225 1226 /** 1227 * Returns the request object. Corresponds to the optional 1228 * {@code request} parameter. 1229 * 1230 * @return The request object, {@code null} if not specified. 1231 */ 1232 public JWT getRequestObject() { 1233 return requestObject; 1234 } 1235 1236 1237 /** 1238 * Returns the request object URI. Corresponds to the optional 1239 * {@code request_uri} parameter. 1240 * 1241 * @return The request object URI, {@code null} if not specified. 1242 */ 1243 public URI getRequestURI() { 1244 return requestURI; 1245 } 1246 1247 1248 /** 1249 * Returns {@code true} if this is a JWT secured authentication 1250 * request. 1251 * 1252 * @return {@code true} if a request object via a {@code request} or 1253 * {@code request_uri} parameter is specified, else 1254 * {@code false}. 1255 */ 1256 public boolean specifiesRequestObject() { 1257 return requestObject != null || requestURI != null; 1258 } 1259 1260 1261 /** 1262 * Returns the requested prompt. Corresponds to the optional 1263 * {@code prompt} parameter. 1264 * 1265 * @return The requested prompt, {@code null} if not specified. 1266 */ 1267 public Prompt getPrompt() { 1268 return prompt; 1269 } 1270 1271 1272 /** 1273 * Returns the DPoP JWK SHA-256 thumbprint. 1274 * 1275 * @return The DPoP JWK SHA-256 thumbprint, {@code null} if not 1276 * specified. 1277 */ 1278 public JWKThumbprintConfirmation getDPoPJWKThumbprintConfirmation() { 1279 return dpopJKT; 1280 } 1281 1282 1283 /** 1284 * Returns the OpenID Connect Federation 1.0 trust chain. 1285 * 1286 * @return The trust chain, {@code null} if not specified. 1287 */ 1288 public TrustChain getTrustChain() { 1289 return trustChain; 1290 } 1291 1292 1293 /** 1294 * Returns the additional custom parameters. 1295 * 1296 * @return The additional custom parameters as a unmodifiable map, 1297 * empty map if none. 1298 */ 1299 public Map<String,List<String>> getCustomParameters () { 1300 return customParams; 1301 } 1302 1303 1304 /** 1305 * Returns the specified custom parameter. 1306 * 1307 * @param name The parameter name. Must not be {@code null}. 1308 * 1309 * @return The parameter value(s), {@code null} if not specified. 1310 */ 1311 public List<String> getCustomParameter(final String name) { 1312 return customParams.get(name); 1313 } 1314 1315 1316 /** 1317 * Returns the URI query parameters for this authorisation request. 1318 * Query parameters which are part of the authorisation endpoint are 1319 * not included. 1320 * 1321 * <p>Example parameters: 1322 * 1323 * <pre> 1324 * response_type = code 1325 * client_id = s6BhdRkqt3 1326 * state = xyz 1327 * redirect_uri = https://client.example.com/cb 1328 * </pre> 1329 * 1330 * @return The parameters. 1331 */ 1332 public Map<String,List<String>> toParameters() { 1333 1334 // Put custom params first, so they may be overwritten by std params 1335 Map<String, List<String>> params = new LinkedHashMap<>(customParams); 1336 1337 params.put("client_id", Collections.singletonList(getClientID().getValue())); 1338 1339 if (getResponseType() != null) 1340 params.put("response_type", Collections.singletonList(getResponseType().toString())); 1341 1342 if (getResponseMode() != null) 1343 params.put("response_mode", Collections.singletonList(getResponseMode().getValue())); 1344 1345 if (getRedirectionURI() != null) 1346 params.put("redirect_uri", Collections.singletonList(getRedirectionURI().toString())); 1347 1348 if (getScope() != null) 1349 params.put("scope", Collections.singletonList(getScope().toString())); 1350 1351 if (getState() != null) 1352 params.put("state", Collections.singletonList(getState().getValue())); 1353 1354 if (getCodeChallenge() != null) { 1355 params.put("code_challenge", Collections.singletonList(getCodeChallenge().getValue())); 1356 1357 if (getCodeChallengeMethod() != null) { 1358 params.put("code_challenge_method", Collections.singletonList(getCodeChallengeMethod().getValue())); 1359 } 1360 } 1361 1362 if (includeGrantedScopes()) 1363 params.put("include_granted_scopes", Collections.singletonList("true")); 1364 1365 if (getResources() != null) 1366 params.put("resource", URIUtils.toStringList(getResources())); 1367 1368 if (getRequestObject() != null) { 1369 try { 1370 params.put("request", Collections.singletonList(getRequestObject().serialize())); 1371 1372 } catch (IllegalStateException e) { 1373 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 1374 } 1375 } 1376 1377 if (getRequestURI() != null) 1378 params.put("request_uri", Collections.singletonList(getRequestURI().toString())); 1379 1380 if (getPrompt() != null) 1381 params.put("prompt", Collections.singletonList(getPrompt().toString())); 1382 1383 if (getDPoPJWKThumbprintConfirmation() != null) 1384 params.put("dpop_jkt", Collections.singletonList(getDPoPJWKThumbprintConfirmation().getValue().toString())); 1385 1386 if (getTrustChain() != null) { 1387 JSONArray jsonArray = new JSONArray(); 1388 jsonArray.addAll(getTrustChain().toSerializedJWTs()); 1389 params.put("trust_chain", Collections.singletonList(jsonArray.toJSONString())); 1390 } 1391 1392 return params; 1393 } 1394 1395 1396 /** 1397 * Returns the parameters for this authorisation request as a JSON Web 1398 * Token (JWT) claims set. Intended for creating a request object. 1399 * 1400 * @return The parameters as JWT claim set. 1401 */ 1402 public JWTClaimsSet toJWTClaimsSet() { 1403 1404 if (specifiesRequestObject()) { 1405 throw new IllegalStateException("Cannot create nested JWT secured authorization request"); 1406 } 1407 1408 Map<String, List<String>> params = toParameters(); 1409 1410 JWTClaimsSet jwtClaimsSet = JWTClaimsSetUtils.toJWTClaimsSet(params); 1411 1412 if (params.containsKey("trust_chain")) { 1413 // JWT claim value must be a JSON array 1414 jwtClaimsSet = new JWTClaimsSet.Builder(jwtClaimsSet) 1415 .claim("trust_chain", getTrustChain().toSerializedJWTs()) 1416 .build(); 1417 } 1418 1419 return jwtClaimsSet; 1420 } 1421 1422 1423 /** 1424 * Returns the URI query string for this authorisation request. 1425 * 1426 * <p>Note that the '?' character preceding the query string in an URI 1427 * is not included in the returned string. 1428 * 1429 * <p>Example URI query string: 1430 * 1431 * <pre> 1432 * response_type=code 1433 * &client_id=s6BhdRkqt3 1434 * &state=xyz 1435 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1436 * </pre> 1437 * 1438 * @return The URI query string. 1439 */ 1440 public String toQueryString() { 1441 1442 Map<String, List<String>> params = new HashMap<>(); 1443 if (getEndpointURI() != null) { 1444 params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery())); 1445 } 1446 params.putAll(toParameters()); 1447 1448 return URLUtils.serializeParameters(params); 1449 } 1450 1451 1452 /** 1453 * Returns the complete URI representation for this authorisation 1454 * request, consisting of the {@link #getEndpointURI authorization 1455 * endpoint URI} with the {@link #toQueryString query string} appended. 1456 * 1457 * <p>Example URI: 1458 * 1459 * <pre> 1460 * https://server.example.com/authorize? 1461 * response_type=code 1462 * &client_id=s6BhdRkqt3 1463 * &state=xyz 1464 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1465 * </pre> 1466 * 1467 * @return The URI representation. 1468 */ 1469 public URI toURI() { 1470 1471 if (getEndpointURI() == null) 1472 throw new SerializeException("The authorization endpoint URI is not specified"); 1473 1474 StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString()); 1475 sb.append('?'); 1476 sb.append(toQueryString()); 1477 try { 1478 return new URI(sb.toString()); 1479 } catch (URISyntaxException e) { 1480 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 1481 } 1482 } 1483 1484 1485 /** 1486 * Returns the matching HTTP request. 1487 * 1488 * @param method The HTTP request method which can be GET or POST. Must 1489 * not be {@code null}. 1490 * 1491 * @return The HTTP request. 1492 */ 1493 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 1494 1495 if (getEndpointURI() == null) 1496 throw new SerializeException("The endpoint URI is not specified"); 1497 1498 HTTPRequest httpRequest; 1499 if (method.equals(HTTPRequest.Method.GET)) { 1500 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, getEndpointURI()); 1501 } else if (method.equals(HTTPRequest.Method.POST)) { 1502 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 1503 } else { 1504 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 1505 } 1506 1507 httpRequest.setQuery(toQueryString()); 1508 1509 return httpRequest; 1510 } 1511 1512 1513 @Override 1514 public HTTPRequest toHTTPRequest() { 1515 1516 return toHTTPRequest(HTTPRequest.Method.GET); 1517 } 1518 1519 1520 /** 1521 * Parses an authorisation request from the specified URI query 1522 * parameters. 1523 * 1524 * <p>Example parameters: 1525 * 1526 * <pre> 1527 * response_type = code 1528 * client_id = s6BhdRkqt3 1529 * state = xyz 1530 * redirect_uri = https://client.example.com/cb 1531 * </pre> 1532 * 1533 * @param params The parameters. Must not be {@code null}. 1534 * 1535 * @return The authorisation request. 1536 * 1537 * @throws ParseException If the parameters couldn't be parsed to an 1538 * authorisation request. 1539 */ 1540 public static AuthorizationRequest parse(final Map<String,List<String>> params) 1541 throws ParseException { 1542 1543 return parse(null, params); 1544 } 1545 1546 1547 /** 1548 * Parses an authorisation request from the specified URI and query 1549 * parameters. 1550 * 1551 * <p>Example parameters: 1552 * 1553 * <pre> 1554 * response_type = code 1555 * client_id = s6BhdRkqt3 1556 * state = xyz 1557 * redirect_uri = https://client.example.com/cb 1558 * </pre> 1559 * 1560 * @param uri The URI of the authorisation endpoint. May be 1561 * {@code null} if the {@link #toHTTPRequest()} method 1562 * will not be used. 1563 * @param params The parameters. Must not be {@code null}. 1564 * 1565 * @return The authorisation request. 1566 * 1567 * @throws ParseException If the parameters couldn't be parsed to an 1568 * authorisation request. 1569 */ 1570 public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params) 1571 throws ParseException { 1572 1573 Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, Collections.singleton("resource")); 1574 1575 if (! repeatParams.isEmpty()) { 1576 // Always result in non-redirecting error. Technically 1577 // only duplicate client_id, state, redirect_uri, 1578 // response_type, request_uri and request should 1579 String msg = "Parameter(s) present more than once: " + repeatParams; 1580 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg)); 1581 } 1582 1583 // Parse response_mode, response_type, client_id, redirect_uri and state first, 1584 // needed if parsing results in a error response 1585 final ClientID clientID; 1586 URI redirectURI = null; 1587 State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state")); 1588 ResponseMode rm = null; 1589 ResponseType rt = null; 1590 1591 // Optional response_mode 1592 String v = MultivaluedMapUtils.getFirstValue(params, "response_mode"); 1593 if (StringUtils.isNotBlank(v)) { 1594 rm = new ResponseMode(v); 1595 } 1596 1597 // Mandatory client_id 1598 v = MultivaluedMapUtils.getFirstValue(params, "client_id"); 1599 if (StringUtils.isBlank(v)) { 1600 // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1 1601 String msg = "Missing client_id parameter"; 1602 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1603 } 1604 clientID = new ClientID(v); 1605 1606 // Optional redirect_uri 1607 v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri"); 1608 if (StringUtils.isNotBlank(v)) { 1609 try { 1610 redirectURI = new URI(v); 1611 } catch (URISyntaxException e) { 1612 // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1 1613 String msg = "Invalid redirect_uri parameter: " + e.getMessage(); 1614 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1615 } 1616 } 1617 1618 // Mandatory response_type, unless in JAR 1619 v = MultivaluedMapUtils.getFirstValue(params, "response_type"); 1620 if (StringUtils.isNotBlank(v)) { 1621 try { 1622 rt = ResponseType.parse(v); 1623 } catch (ParseException e) { 1624 // Only cause 1625 String msg = "Invalid response_type parameter"; 1626 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1627 clientID, redirectURI, rm, state, e); 1628 } 1629 } 1630 1631 // Check for a JAR in request or request_uri parameters 1632 v = MultivaluedMapUtils.getFirstValue(params, "request_uri"); 1633 URI requestURI = null; 1634 if (StringUtils.isNotBlank(v)) { 1635 try { 1636 requestURI = new URI(v); 1637 } catch (URISyntaxException e) { 1638 String msg = "Invalid request_uri parameter: " + e.getMessage(); 1639 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1640 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1641 } 1642 } 1643 1644 v = MultivaluedMapUtils.getFirstValue(params, "request"); 1645 1646 JWT requestObject = null; 1647 1648 if (StringUtils.isNotBlank(v)) { 1649 1650 // request_object and request_uri must not be present at the same time 1651 if (requestURI != null) { 1652 String msg = "Invalid request: Found mutually exclusive request and request_uri parameters"; 1653 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1654 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null); 1655 } 1656 1657 try { 1658 requestObject = JWTParser.parse(v); 1659 1660 if (requestObject instanceof SignedJWT) { 1661 // Make sure the "sub" claim is not set to the client_id value 1662 // https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-29#section-10.8 1663 JWTClaimsSet requestObjectClaims = requestObject.getJWTClaimsSet(); 1664 if (clientID.getValue().equals(requestObjectClaims.getSubject())) { 1665 throw new java.text.ParseException("The JWT sub (subject) claim must not equal the client_id", 0); 1666 } 1667 } 1668 1669 } catch (java.text.ParseException e) { 1670 String msg = "Invalid request parameter: " + e.getMessage(); 1671 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1672 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1673 } 1674 } 1675 1676 // Response type mandatory, unless in JAR 1677 if (rt == null && requestObject == null && requestURI == null) { 1678 String msg = "Missing response_type parameter"; 1679 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1680 clientID, redirectURI, ResponseMode.resolve(rm, null), state, null); 1681 } 1682 1683 1684 // Parse optional scope 1685 v = MultivaluedMapUtils.getFirstValue(params, "scope"); 1686 1687 Scope scope = null; 1688 1689 if (StringUtils.isNotBlank(v)) 1690 scope = Scope.parse(v); 1691 1692 1693 // Parse optional code challenge and method for PKCE 1694 CodeChallenge codeChallenge = null; 1695 CodeChallengeMethod codeChallengeMethod = null; 1696 1697 v = MultivaluedMapUtils.getFirstValue(params, "code_challenge"); 1698 1699 if (StringUtils.isNotBlank(v)) 1700 codeChallenge = CodeChallenge.parse(v); 1701 1702 if (codeChallenge != null) { 1703 1704 v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method"); 1705 1706 if (StringUtils.isNotBlank(v)) 1707 codeChallengeMethod = CodeChallengeMethod.parse(v); 1708 } 1709 1710 1711 List<URI> resources; 1712 try { 1713 resources = ResourceUtils.parseResourceURIs(params.get("resource")); 1714 } catch (ParseException e) { 1715 throw new ParseException(e.getMessage(), OAuth2Error.INVALID_TARGET.setDescription(e.getMessage()), 1716 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1717 } 1718 1719 boolean includeGrantedScopes = false; 1720 v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes"); 1721 if ("true".equals(v)) { 1722 includeGrantedScopes = true; 1723 } 1724 1725 Prompt prompt; 1726 try { 1727 prompt = Prompt.parse(MultivaluedMapUtils.getFirstValue(params, "prompt")); 1728 1729 } catch (ParseException e) { 1730 String msg = "Invalid prompt parameter: " + e.getMessage(); 1731 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1732 clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e); 1733 } 1734 1735 JWKThumbprintConfirmation dpopJKT = null; 1736 v = MultivaluedMapUtils.getFirstValue(params, "dpop_jkt"); 1737 if (StringUtils.isNotBlank(v)) { 1738 dpopJKT = new JWKThumbprintConfirmation(new Base64URL(v)); 1739 } 1740 1741 TrustChain trustChain = null; 1742 v = MultivaluedMapUtils.getFirstValue(params, "trust_chain"); 1743 if (StringUtils.isNotBlank(v)) { 1744 JSONArray jsonArray = JSONArrayUtils.parse(v); 1745 trustChain = TrustChain.parseSerialized(JSONArrayUtils.toStringList(jsonArray)); 1746 } 1747 1748 // Parse custom parameters 1749 Map<String,List<String>> customParams = null; 1750 1751 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1752 1753 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1754 // We have a custom parameter 1755 if (customParams == null) { 1756 customParams = new HashMap<>(); 1757 } 1758 customParams.put(p.getKey(), p.getValue()); 1759 } 1760 } 1761 1762 1763 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state, 1764 codeChallenge, codeChallengeMethod, resources, includeGrantedScopes, 1765 requestObject, requestURI, 1766 prompt, dpopJKT, trustChain, 1767 customParams); 1768 } 1769 1770 1771 /** 1772 * Parses an authorisation request from the specified URI query string. 1773 * 1774 * <p>Example URI query string: 1775 * 1776 * <pre> 1777 * response_type=code 1778 * &client_id=s6BhdRkqt3 1779 * &state=xyz 1780 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1781 * </pre> 1782 * 1783 * @param query The URI query string. Must not be {@code null}. 1784 * 1785 * @return The authorisation request. 1786 * 1787 * @throws ParseException If the query string couldn't be parsed to an 1788 * authorisation request. 1789 */ 1790 public static AuthorizationRequest parse(final String query) 1791 throws ParseException { 1792 1793 return parse(null, URLUtils.parseParameters(query)); 1794 } 1795 1796 1797 /** 1798 * Parses an authorisation request from the specified URI and query 1799 * string. 1800 * 1801 * <p>Example URI query string: 1802 * 1803 * <pre> 1804 * response_type=code 1805 * &client_id=s6BhdRkqt3 1806 * &state=xyz 1807 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1808 * </pre> 1809 * 1810 * @param uri The URI of the authorisation endpoint. May be 1811 * {@code null} if the {@link #toHTTPRequest()} method 1812 * will not be used. 1813 * @param query The URI query string. Must not be {@code null}. 1814 * 1815 * @return The authorisation request. 1816 * 1817 * @throws ParseException If the query string couldn't be parsed to an 1818 * authorisation request. 1819 */ 1820 public static AuthorizationRequest parse(final URI uri, final String query) 1821 throws ParseException { 1822 1823 return parse(uri, URLUtils.parseParameters(query)); 1824 } 1825 1826 1827 /** 1828 * Parses an authorisation request from the specified URI. 1829 * 1830 * <p>Example URI: 1831 * 1832 * <pre> 1833 * https://server.example.com/authorize? 1834 * response_type=code 1835 * &client_id=s6BhdRkqt3 1836 * &state=xyz 1837 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1838 * </pre> 1839 * 1840 * @param uri The URI. Must not be {@code null}. 1841 * 1842 * @return The authorisation request. 1843 * 1844 * @throws ParseException If the URI couldn't be parsed to an 1845 * authorisation request. 1846 */ 1847 public static AuthorizationRequest parse(final URI uri) 1848 throws ParseException { 1849 1850 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1851 } 1852 1853 1854 /** 1855 * Parses an authorisation request from the specified HTTP request. 1856 * 1857 * <p>Example HTTP request (GET): 1858 * 1859 * <pre> 1860 * https://server.example.com/authorize? 1861 * response_type=code 1862 * &client_id=s6BhdRkqt3 1863 * &state=xyz 1864 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 1865 * </pre> 1866 * 1867 * @param httpRequest The HTTP request. Must not be {@code null}. 1868 * 1869 * @return The authorisation request. 1870 * 1871 * @throws ParseException If the HTTP request couldn't be parsed to an 1872 * authorisation request. 1873 */ 1874 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 1875 throws ParseException { 1876 1877 String query = httpRequest.getQuery(); 1878 1879 if (query == null) 1880 throw new ParseException("Missing URI query string"); 1881 1882 return parse(URIUtils.getBaseURI(httpRequest.getURI()), query); 1883 } 1884}