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