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