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