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