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