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