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 public Map<String,String> toParameters() { 435 436 Map <String,String> params = new LinkedHashMap<>(); 437 438 params.put("response_type", rt.toString()); 439 params.put("client_id", clientID.getValue()); 440 441 if (rm != null) { 442 params.put("response_mode", rm.getValue()); 443 } 444 445 if (redirectURI != null) 446 params.put("redirect_uri", redirectURI.toString()); 447 448 if (scope != null) 449 params.put("scope", scope.toString()); 450 451 if (state != null) 452 params.put("state", state.getValue()); 453 454 return params; 455 } 456 457 458 /** 459 * Returns the URI query string for this authorisation request. 460 * 461 * <p>Note that the '?' character preceding the query string in an URI 462 * is not included in the returned string. 463 * 464 * <p>Example URI query string: 465 * 466 * <pre> 467 * response_type=code 468 * &client_id=s6BhdRkqt3 469 * &state=xyz 470 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 471 * </pre> 472 * 473 * @return The URI query string. 474 */ 475 public String toQueryString() { 476 477 return URLUtils.serializeParameters(toParameters()); 478 } 479 480 481 /** 482 * Returns the complete URI representation for this authorisation 483 * request, consisting of the {@link #getEndpointURI authorization 484 * endpoint URI} with the {@link #toQueryString query string} appended. 485 * 486 * <p>Example URI: 487 * 488 * <pre> 489 * https://server.example.com/authorize? 490 * response_type=code 491 * &client_id=s6BhdRkqt3 492 * &state=xyz 493 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 494 * </pre> 495 * 496 * @return The URI representation. 497 */ 498 public URI toURI() { 499 500 if (getEndpointURI() == null) 501 throw new SerializeException("The authorization endpoint URI is not specified"); 502 503 StringBuilder sb = new StringBuilder(getEndpointURI().toString()); 504 sb.append('?'); 505 sb.append(toQueryString()); 506 try { 507 return new URI(sb.toString()); 508 } catch (URISyntaxException e) { 509 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 510 } 511 } 512 513 514 /** 515 * Returns the matching HTTP request. 516 * 517 * @param method The HTTP request method which can be GET or POST. Must 518 * not be {@code null}. 519 * 520 * @return The HTTP request. 521 */ 522 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) { 523 524 if (getEndpointURI() == null) 525 throw new SerializeException("The endpoint URI is not specified"); 526 527 HTTPRequest httpRequest; 528 529 URL endpointURL; 530 531 try { 532 endpointURL = getEndpointURI().toURL(); 533 534 } catch (MalformedURLException e) { 535 536 throw new SerializeException(e.getMessage(), e); 537 } 538 539 if (method.equals(HTTPRequest.Method.GET)) { 540 541 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 542 543 } else if (method.equals(HTTPRequest.Method.POST)) { 544 545 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 546 547 } else { 548 549 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 550 } 551 552 httpRequest.setQuery(toQueryString()); 553 554 return httpRequest; 555 } 556 557 558 @Override 559 public HTTPRequest toHTTPRequest() { 560 561 return toHTTPRequest(HTTPRequest.Method.GET); 562 } 563 564 565 /** 566 * Parses an authorisation request from the specified parameters. 567 * 568 * <p>Example parameters: 569 * 570 * <pre> 571 * response_type = code 572 * client_id = s6BhdRkqt3 573 * state = xyz 574 * redirect_uri = https://client.example.com/cb 575 * </pre> 576 * 577 * @param params The parameters. Must not be {@code null}. 578 * 579 * @return The authorisation request. 580 * 581 * @throws ParseException If the parameters couldn't be parsed to an 582 * authorisation request. 583 */ 584 public static AuthorizationRequest parse(final Map<String,String> params) 585 throws ParseException { 586 587 return parse(null, params); 588 } 589 590 591 /** 592 * Parses an authorisation request from the specified parameters. 593 * 594 * <p>Example parameters: 595 * 596 * <pre> 597 * response_type = code 598 * client_id = s6BhdRkqt3 599 * state = xyz 600 * redirect_uri = https://client.example.com/cb 601 * </pre> 602 * 603 * @param uri The URI of the authorisation endpoint. May be 604 * {@code null} if the {@link #toHTTPRequest()} method 605 * will not be used. 606 * @param params The parameters. Must not be {@code null}. 607 * 608 * @return The authorisation request. 609 * 610 * @throws ParseException If the parameters couldn't be parsed to an 611 * authorisation request. 612 */ 613 public static AuthorizationRequest parse(final URI uri, final Map<String,String> params) 614 throws ParseException { 615 616 // Parse mandatory client ID first 617 String v = params.get("client_id"); 618 619 if (StringUtils.isBlank(v)) { 620 String msg = "Missing \"client_id\" parameter"; 621 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 622 } 623 624 ClientID clientID = new ClientID(v); 625 626 627 // Parse optional redirection URI second 628 v = params.get("redirect_uri"); 629 630 URI redirectURI = null; 631 632 if (StringUtils.isNotBlank(v)) { 633 634 try { 635 redirectURI = new URI(v); 636 637 } catch (URISyntaxException e) { 638 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 639 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 640 clientID, null, null, null, e); 641 } 642 } 643 644 645 // Parse optional state third 646 State state = State.parse(params.get("state")); 647 648 649 // Parse mandatory response type 650 v = params.get("response_type"); 651 652 ResponseType rt; 653 654 try { 655 rt = ResponseType.parse(v); 656 657 } catch (ParseException e) { 658 // Only cause 659 String msg = "Missing \"response_type\" parameter"; 660 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 661 clientID, redirectURI, null, state, e); 662 } 663 664 665 // Parse the optional response mode 666 v = params.get("response_mode"); 667 668 ResponseMode rm = null; 669 670 if (StringUtils.isNotBlank(v)) { 671 rm = new ResponseMode(v); 672 } 673 674 675 // Parse optional scope 676 v = params.get("scope"); 677 678 Scope scope = null; 679 680 if (StringUtils.isNotBlank(v)) 681 scope = Scope.parse(v); 682 683 684 return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state); 685 } 686 687 688 /** 689 * Parses an authorisation request from the specified URI query string. 690 * 691 * <p>Example URI query string: 692 * 693 * <pre> 694 * response_type=code 695 * &client_id=s6BhdRkqt3 696 * &state=xyz 697 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 698 * </pre> 699 * 700 * @param query The URI query string. Must not be {@code null}. 701 * 702 * @return The authorisation request. 703 * 704 * @throws ParseException If the query string couldn't be parsed to an 705 * authorisation request. 706 */ 707 public static AuthorizationRequest parse(final String query) 708 throws ParseException { 709 710 return parse(null, URLUtils.parseParameters(query)); 711 } 712 713 714 /** 715 * Parses an authorisation request from the specified URI query string. 716 * 717 * <p>Example URI query string: 718 * 719 * <pre> 720 * response_type=code 721 * &client_id=s6BhdRkqt3 722 * &state=xyz 723 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 724 * </pre> 725 * 726 * @param uri The URI of the authorisation endpoint. May be 727 * {@code null} if the {@link #toHTTPRequest()} method 728 * will not be used. 729 * @param query The URI query string. Must not be {@code null}. 730 * 731 * @return The authorisation request. 732 * 733 * @throws ParseException If the query string couldn't be parsed to an 734 * authorisation request. 735 */ 736 public static AuthorizationRequest parse(final URI uri, final String query) 737 throws ParseException { 738 739 return parse(uri, URLUtils.parseParameters(query)); 740 } 741 742 743 /** 744 * Parses an authorisation request from the specified URI. 745 * 746 * <p>Example URI: 747 * 748 * <pre> 749 * https://server.example.com/authorize? 750 * response_type=code 751 * &client_id=s6BhdRkqt3 752 * &state=xyz 753 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 754 * </pre> 755 * 756 * @param uri The URI. Must not be {@code null}. 757 * 758 * @return The authorisation request. 759 * 760 * @throws ParseException If the URI couldn't be parsed to an 761 * authorisation request. 762 */ 763 public static AuthorizationRequest parse(final URI uri) 764 throws ParseException { 765 766 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 767 } 768 769 770 /** 771 * Parses an authorisation request from the specified HTTP request. 772 * 773 * <p>Example HTTP request (GET): 774 * 775 * <pre> 776 * https://server.example.com/authorize? 777 * response_type=code 778 * &client_id=s6BhdRkqt3 779 * &state=xyz 780 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 781 * </pre> 782 * 783 * @param httpRequest The HTTP request. Must not be {@code null}. 784 * 785 * @return The authorisation request. 786 * 787 * @throws ParseException If the HTTP request couldn't be parsed to an 788 * authorisation request. 789 */ 790 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 791 throws ParseException { 792 793 String query = httpRequest.getQuery(); 794 795 if (query == null) 796 throw new ParseException("Missing URI query string"); 797 798 try { 799 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 800 801 } catch (URISyntaxException e) { 802 803 throw new ParseException(e.getMessage(), e); 804 } 805 } 806}