001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.LinkedHashMap; 009import java.util.Map; 010 011import net.jcip.annotations.Immutable; 012 013import org.apache.commons.lang3.StringUtils; 014 015import com.nimbusds.oauth2.sdk.id.ClientID; 016import com.nimbusds.oauth2.sdk.id.State; 017import com.nimbusds.oauth2.sdk.http.HTTPRequest; 018import com.nimbusds.oauth2.sdk.util.URIUtils; 019import com.nimbusds.oauth2.sdk.util.URLUtils; 020 021 022/** 023 * Authorisation request. Used to authenticate an end-user and request the 024 * end-user's consent to grant the client access to a protected resource. 025 * 026 * <p>Extending classes may define additional request parameters as well as 027 * enforce tighter requirements on the base parameters. 028 * 029 * <p>Example HTTP request: 030 * 031 * <pre> 032 * https://server.example.com/authorize? 033 * response_type=code 034 * &client_id=s6BhdRkqt3 035 * &state=xyz 036 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 037 * </pre> 038 * 039 * <p>Related specifications: 040 * 041 * <ul> 042 * <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1. 043 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 044 * <li>OAuth 2.0 Form Post Response Mode 1.0. 045 * </ul> 046 */ 047@Immutable 048public class AuthorizationRequest extends AbstractRequest { 049 050 051 /** 052 * The response type (required). 053 */ 054 private final ResponseType rt; 055 056 057 /** 058 * The client identifier (required). 059 */ 060 private final ClientID clientID; 061 062 063 /** 064 * The redirection URI where the response will be sent (optional). 065 */ 066 private final URI redirectURI; 067 068 069 /** 070 * The scope (optional). 071 */ 072 private final Scope scope; 073 074 075 /** 076 * The opaque value to maintain state between the request and the 077 * callback (recommended). 078 */ 079 private final State state; 080 081 082 /** 083 * The response mode (optional). 084 */ 085 private final ResponseMode rm; 086 087 088 /** 089 * Builder for constructing authorisation requests. 090 */ 091 public static class Builder { 092 093 094 /** 095 * The endpoint URI (optional). 096 */ 097 private URI uri; 098 099 100 /** 101 * The response type (required). 102 */ 103 private final ResponseType rt; 104 105 106 /** 107 * The client identifier (required). 108 */ 109 private final ClientID clientID; 110 111 112 /** 113 * The redirection URI where the response will be sent 114 * (optional). 115 */ 116 private URI redirectURI; 117 118 119 /** 120 * The scope (optional). 121 */ 122 private Scope scope; 123 124 125 /** 126 * The opaque value to maintain state between the request and 127 * the callback (recommended). 128 */ 129 private State state; 130 131 132 /** 133 * The response mode (optional). 134 */ 135 private ResponseMode rm; 136 137 138 /** 139 * Creates a new authorisation request builder. 140 * 141 * @param rt The response type. Corresponds to the 142 * {@code response_type} parameter. Must not be 143 * {@code null}. 144 * @param clientID The client identifier. Corresponds to the 145 * {@code client_id} parameter. Must not be 146 * {@code null}. 147 */ 148 public Builder(final ResponseType rt, final ClientID clientID) { 149 150 if (rt == null) 151 throw new IllegalArgumentException("The response type must not be null"); 152 153 this.rt = rt; 154 155 156 if (clientID == null) 157 throw new IllegalArgumentException("The client ID must not be null"); 158 159 this.clientID = clientID; 160 } 161 162 163 /** 164 * Sets the redirection URI. Corresponds to the optional 165 * {@code redirection_uri} parameter. 166 * 167 * @param redirectURI The redirection URI, {@code null} if not 168 * specified. 169 * 170 * @return This builder. 171 */ 172 public Builder redirectionURI(final URI redirectURI) { 173 174 this.redirectURI = redirectURI; 175 return this; 176 } 177 178 179 /** 180 * Sets the scope. Corresponds to the optional {@code scope} 181 * parameter. 182 * 183 * @param scope The scope, {@code null} if not specified. 184 * 185 * @return This builder. 186 */ 187 public Builder scope(final Scope scope) { 188 189 this.scope = scope; 190 return this; 191 } 192 193 194 /** 195 * Sets the state. Corresponds to the recommended {@code state} 196 * parameter. 197 * 198 * @param state The state, {@code null} if not specified. 199 * 200 * @return This builder. 201 */ 202 public Builder state(final State state) { 203 204 this.state = state; 205 return this; 206 } 207 208 209 /** 210 * Sets the response mode. Corresponds to the optional 211 * {@code response_mode} parameter. Use of this parameter is 212 * not recommended unless a non-default response mode is 213 * requested (e.g. form_post). 214 * 215 * @param rm The response mode, {@code null} if not specified. 216 * 217 * @return This builder. 218 */ 219 public Builder responseMode(final ResponseMode rm) { 220 221 this.rm = rm; 222 return this; 223 } 224 225 226 /** 227 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 228 * request is intended. 229 * 230 * @param uri The endpoint URI, {@code null} if not specified. 231 * 232 * @return This builder. 233 */ 234 public Builder endpointURI(final URI uri) { 235 236 this.uri = uri; 237 return this; 238 } 239 240 241 /** 242 * Builds a new authorisation request. 243 * 244 * @return The authorisation request. 245 */ 246 public AuthorizationRequest build() { 247 248 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state); 249 } 250 } 251 252 253 /** 254 * Creates a new minimal authorisation request. 255 * 256 * @param uri The URI of the authorisation endpoint. May be 257 * {@code null} if the {@link #toHTTPRequest} method 258 * will not be used. 259 * @param rt The response type. Corresponds to the 260 * {@code response_type} parameter. Must not be 261 * {@code null}. 262 * @param clientID The client identifier. Corresponds to the 263 * {@code client_id} parameter. Must not be 264 * {@code null}. 265 */ 266 public AuthorizationRequest(final URI uri, 267 final ResponseType rt, 268 final ClientID clientID) { 269 270 this(uri, rt, null, clientID, null, null, null); 271 } 272 273 274 /** 275 * Creates a new authorisation request. 276 * 277 * @param uri The URI of the authorisation endpoint. May be 278 * {@code null} if the {@link #toHTTPRequest} method 279 * will not be used. 280 * @param rt The response type. Corresponds to the 281 * {@code response_type} parameter. Must not be 282 * {@code null}. 283 * @param rm The response mode. Corresponds to the optional 284 * {@code response_mode} parameter. Use of this 285 * parameter is not recommended unless a non-default 286 * response mode is requested (e.g. form_post). 287 * @param clientID The client identifier. Corresponds to the 288 * {@code client_id} parameter. Must not be 289 * {@code null}. 290 * @param redirectURI The redirection URI. Corresponds to the optional 291 * {@code redirect_uri} parameter. {@code null} if 292 * not specified. 293 * @param scope The request scope. Corresponds to the optional 294 * {@code scope} parameter. {@code null} if not 295 * specified. 296 * @param state The state. Corresponds to the recommended 297 * {@code state} parameter. {@code null} if not 298 * specified. 299 */ 300 public AuthorizationRequest(final URI uri, 301 final ResponseType rt, 302 final ResponseMode rm, 303 final ClientID clientID, 304 final URI redirectURI, 305 final Scope scope, 306 final State state) { 307 308 super(uri); 309 310 if (rt == null) 311 throw new IllegalArgumentException("The response type must not be null"); 312 313 this.rt = rt; 314 315 this.rm = rm; 316 317 318 if (clientID == null) 319 throw new IllegalArgumentException("The client ID must not be null"); 320 321 this.clientID = clientID; 322 323 324 this.redirectURI = redirectURI; 325 this.scope = scope; 326 this.state = state; 327 } 328 329 330 /** 331 * Gets the response type. Corresponds to the {@code response_type} 332 * parameter. 333 * 334 * @return The response type. 335 */ 336 public ResponseType getResponseType() { 337 338 return rt; 339 } 340 341 342 /** 343 * Gets the optional response mode. Corresponds to the optional 344 * {@code response_mode} parameter. 345 * 346 * @return The response mode, {@code null} if not specified. 347 */ 348 public ResponseMode getResponseMode() { 349 350 return rm; 351 } 352 353 354 /** 355 * Returns the implied response mode, determined by the optional 356 * {@code response_mode} parameter, and if that isn't specified, by 357 * the {@code response_type}. 358 * 359 * @return The implied response mode. 360 */ 361 public ResponseMode impliedResponseMode() { 362 363 if (rm != null) { 364 return rm; 365 } else if (rt.impliesImplicitFlow()) { 366 return ResponseMode.FRAGMENT; 367 } else { 368 return ResponseMode.QUERY; 369 } 370 } 371 372 373 /** 374 * Gets the client identifier. Corresponds to the {@code client_id} 375 * parameter. 376 * 377 * @return The client identifier. 378 */ 379 public ClientID getClientID() { 380 381 return clientID; 382 } 383 384 385 /** 386 * Gets the redirection URI. Corresponds to the optional 387 * {@code redirection_uri} parameter. 388 * 389 * @return The redirection URI, {@code null} if not specified. 390 */ 391 public URI getRedirectionURI() { 392 393 return redirectURI; 394 } 395 396 397 /** 398 * Gets the scope. Corresponds to the optional {@code scope} parameter. 399 * 400 * @return The scope, {@code null} if not specified. 401 */ 402 public Scope getScope() { 403 404 return scope; 405 } 406 407 408 /** 409 * Gets the state. Corresponds to the recommended {@code state} 410 * parameter. 411 * 412 * @return The state, {@code null} if not specified. 413 */ 414 public State getState() { 415 416 return state; 417 } 418 419 420 /** 421 * Returns the parameters for this authorisation request. 422 * 423 * <p>Example parameters: 424 * 425 * <pre> 426 * response_type = code 427 * client_id = s6BhdRkqt3 428 * state = xyz 429 * redirect_uri = https://client.example.com/cb 430 * </pre> 431 * 432 * @return The parameters. 433 * 434 * @throws SerializeException If this authorisation request couldn't be 435 * serialised to an parameters map. 436 */ 437 public Map<String,String> toParameters() 438 throws SerializeException { 439 440 Map <String,String> params = new LinkedHashMap<>(); 441 442 params.put("response_type", rt.toString()); 443 params.put("client_id", clientID.getValue()); 444 445 if (rm != null) { 446 params.put("response_mode", rm.getValue()); 447 } 448 449 if (redirectURI != null) 450 params.put("redirect_uri", redirectURI.toString()); 451 452 if (scope != null) 453 params.put("scope", scope.toString()); 454 455 if (state != null) 456 params.put("state", state.getValue()); 457 458 return params; 459 } 460 461 462 /** 463 * Returns the URI query string for this authorisation request. 464 * 465 * <p>Note that the '?' character preceding the query string in an URI 466 * is not included in the returned string. 467 * 468 * <p>Example URI query string: 469 * 470 * <pre> 471 * response_type=code 472 * &client_id=s6BhdRkqt3 473 * &state=xyz 474 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 475 * </pre> 476 * 477 * @return The URI query string. 478 * 479 * @throws SerializeException If this authorisation request couldn't be 480 * serialised to an URI query string. 481 */ 482 public String toQueryString() 483 throws SerializeException { 484 485 return URLUtils.serializeParameters(toParameters()); 486 } 487 488 489 /** 490 * Returns the complete URI representation for this authorisation 491 * request, consisting of the {@link #getEndpointURI authorization 492 * endpoint URI} with the {@link #toQueryString query string} appended. 493 * 494 * <p>Example URI: 495 * 496 * <pre> 497 * https://server.example.com/authorize? 498 * response_type=code 499 * &client_id=s6BhdRkqt3 500 * &state=xyz 501 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 502 * </pre> 503 * 504 * @return The URI representation. 505 * 506 * @throws SerializeException If this authorisation request couldn't be 507 * serialised to a URI. 508 */ 509 public URI toURI() 510 throws SerializeException { 511 512 if (getEndpointURI() == null) 513 throw new SerializeException("The authorization endpoint URI is not specified"); 514 515 StringBuilder sb = new StringBuilder(getEndpointURI().toString()); 516 sb.append('?'); 517 sb.append(toQueryString()); 518 try { 519 return new URI(sb.toString()); 520 } catch (URISyntaxException e) { 521 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 522 } 523 } 524 525 526 /** 527 * Returns the matching HTTP request. 528 * 529 * @param method The HTTP request method which can be GET or POST. Must 530 * not be {@code null}. 531 * 532 * @return The HTTP request. 533 * 534 * @throws SerializeException If the authorisation request message 535 * couldn't be serialised to an HTTP 536 * request. 537 */ 538 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) 539 throws SerializeException { 540 541 if (getEndpointURI() == null) 542 throw new SerializeException("The endpoint URI is not specified"); 543 544 HTTPRequest httpRequest; 545 546 URL endpointURL; 547 548 try { 549 endpointURL = getEndpointURI().toURL(); 550 551 } catch (MalformedURLException e) { 552 553 throw new SerializeException(e.getMessage(), e); 554 } 555 556 if (method.equals(HTTPRequest.Method.GET)) { 557 558 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 559 560 } else if (method.equals(HTTPRequest.Method.POST)) { 561 562 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 563 564 } else { 565 566 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 567 } 568 569 httpRequest.setQuery(toQueryString()); 570 571 return httpRequest; 572 } 573 574 575 @Override 576 public HTTPRequest toHTTPRequest() 577 throws SerializeException { 578 579 return toHTTPRequest(HTTPRequest.Method.GET); 580 } 581 582 583 /** 584 * Parses an authorisation request from the specified parameters. 585 * 586 * <p>Example parameters: 587 * 588 * <pre> 589 * response_type = code 590 * client_id = s6BhdRkqt3 591 * state = xyz 592 * redirect_uri = https://client.example.com/cb 593 * </pre> 594 * 595 * @param params The parameters. Must not be {@code null}. 596 * 597 * @return The authorisation request. 598 * 599 * @throws ParseException If the parameters couldn't be parsed to an 600 * authorisation request. 601 */ 602 public static AuthorizationRequest parse(final Map<String,String> params) 603 throws ParseException { 604 605 return parse(null, params); 606 } 607 608 609 /** 610 * Parses an authorisation request from the specified parameters. 611 * 612 * <p>Example parameters: 613 * 614 * <pre> 615 * response_type = code 616 * client_id = s6BhdRkqt3 617 * state = xyz 618 * redirect_uri = https://client.example.com/cb 619 * </pre> 620 * 621 * @param uri The URI of the authorisation endpoint. May be 622 * {@code null} if the {@link #toHTTPRequest()} method 623 * will not be used. 624 * @param params The parameters. Must not be {@code null}. 625 * 626 * @return The authorisation request. 627 * 628 * @throws ParseException If the parameters couldn't be parsed to an 629 * authorisation request. 630 */ 631 public static AuthorizationRequest parse(final URI uri, final Map<String,String> params) 632 throws ParseException { 633 634 // Parse mandatory client ID first 635 String v = params.get("client_id"); 636 637 if (StringUtils.isBlank(v)) { 638 String msg = "Missing \"client_id\" parameter"; 639 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 640 } 641 642 ClientID clientID = new ClientID(v); 643 644 645 // Parse optional redirection URI second 646 v = params.get("redirect_uri"); 647 648 URI redirectURI = null; 649 650 if (StringUtils.isNotBlank(v)) { 651 652 try { 653 redirectURI = new URI(v); 654 655 } catch (URISyntaxException e) { 656 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 657 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 658 clientID, null, null, null, e); 659 } 660 } 661 662 663 // Parse optional state third 664 State state = State.parse(params.get("state")); 665 666 667 // Parse mandatory response type 668 v = params.get("response_type"); 669 670 ResponseType rt; 671 672 try { 673 rt = ResponseType.parse(v); 674 675 } catch (ParseException e) { 676 // Only cause 677 String msg = "Missing \"response_type\" parameter"; 678 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 679 clientID, redirectURI, null, state, e); 680 } 681 682 683 // Parse the optional response mode 684 v = params.get("response_mode"); 685 686 ResponseMode rm = null; 687 688 if (StringUtils.isNotBlank(v)) { 689 rm = new ResponseMode(v); 690 } 691 692 693 // Parse optional scope 694 v = params.get("scope"); 695 696 Scope scope = null; 697 698 if (StringUtils.isNotBlank(v)) 699 scope = Scope.parse(v); 700 701 702 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state); 703 } 704 705 706 /** 707 * Parses an authorisation request from the specified URI query string. 708 * 709 * <p>Example URI query string: 710 * 711 * <pre> 712 * response_type=code 713 * &client_id=s6BhdRkqt3 714 * &state=xyz 715 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 716 * </pre> 717 * 718 * @param query The URI query string. Must not be {@code null}. 719 * 720 * @return The authorisation request. 721 * 722 * @throws ParseException If the query string couldn't be parsed to an 723 * authorisation request. 724 */ 725 public static AuthorizationRequest parse(final String query) 726 throws ParseException { 727 728 return parse(null, URLUtils.parseParameters(query)); 729 } 730 731 732 /** 733 * Parses an authorisation request from the specified URI query string. 734 * 735 * <p>Example URI query string: 736 * 737 * <pre> 738 * response_type=code 739 * &client_id=s6BhdRkqt3 740 * &state=xyz 741 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 742 * </pre> 743 * 744 * @param uri The URI of the authorisation endpoint. May be 745 * {@code null} if the {@link #toHTTPRequest()} method 746 * will not be used. 747 * @param query The URI query string. Must not be {@code null}. 748 * 749 * @return The authorisation request. 750 * 751 * @throws ParseException If the query string couldn't be parsed to an 752 * authorisation request. 753 */ 754 public static AuthorizationRequest parse(final URI uri, final String query) 755 throws ParseException { 756 757 return parse(uri, URLUtils.parseParameters(query)); 758 } 759 760 761 /** 762 * Parses an authorisation request from the specified URI. 763 * 764 * <p>Example URI: 765 * 766 * <pre> 767 * https://server.example.com/authorize? 768 * response_type=code 769 * &client_id=s6BhdRkqt3 770 * &state=xyz 771 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 772 * </pre> 773 * 774 * @param uri The URI. Must not be {@code null}. 775 * 776 * @return The authorisation request. 777 * 778 * @throws ParseException If the URI couldn't be parsed to an 779 * authorisation request. 780 */ 781 public static AuthorizationRequest parse(final URI uri) 782 throws ParseException { 783 784 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 785 } 786 787 788 /** 789 * Parses an authorisation request from the specified HTTP request. 790 * 791 * <p>Example HTTP request (GET): 792 * 793 * <pre> 794 * https://server.example.com/authorize? 795 * response_type=code 796 * &client_id=s6BhdRkqt3 797 * &state=xyz 798 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 799 * </pre> 800 * 801 * @param httpRequest The HTTP request. Must not be {@code null}. 802 * 803 * @return The authorisation request. 804 * 805 * @throws ParseException If the HTTP request couldn't be parsed to an 806 * authorisation request. 807 */ 808 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 809 throws ParseException { 810 811 String query = httpRequest.getQuery(); 812 813 if (query == null) 814 throw new ParseException("Missing URI query string"); 815 816 try { 817 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 818 819 } catch (URISyntaxException e) { 820 821 throw new ParseException(e.getMessage(), e); 822 } 823 } 824}