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<>(); 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 String msg = "Missing \"client_id\" parameter"; 566 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 567 } 568 569 ClientID clientID = new ClientID(v); 570 571 572 // Parse optional redirection URI second 573 v = params.get("redirect_uri"); 574 575 URI redirectURI = null; 576 577 if (StringUtils.isNotBlank(v)) { 578 579 try { 580 redirectURI = new URI(v); 581 582 } catch (URISyntaxException e) { 583 String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage(); 584 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 585 clientID, null, null, e); 586 } 587 } 588 589 590 // Parse optional state third 591 State state = State.parse(params.get("state")); 592 593 594 // Parse mandatory response type 595 v = params.get("response_type"); 596 597 ResponseType rt; 598 599 try { 600 rt = ResponseType.parse(v); 601 602 } catch (ParseException e) { 603 // Only cause 604 String msg = "Missing \"response_type\" parameter"; 605 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 606 clientID, redirectURI, state, e); 607 } 608 609 610 // Parse optional scope 611 v = params.get("scope"); 612 613 Scope scope = null; 614 615 if (StringUtils.isNotBlank(v)) 616 scope = Scope.parse(v); 617 618 619 return new AuthorizationRequest(uri, rt, clientID, redirectURI, scope, state); 620 } 621 622 623 /** 624 * Parses an authorisation request from the specified URI query string. 625 * 626 * <p>Example URI query string: 627 * 628 * <pre> 629 * response_type=code 630 * &client_id=s6BhdRkqt3 631 * &state=xyz 632 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 633 * </pre> 634 * 635 * @param query The URI query string. Must not be {@code null}. 636 * 637 * @return The authorisation request. 638 * 639 * @throws ParseException If the query string couldn't be parsed to an 640 * authorisation request. 641 */ 642 public static AuthorizationRequest parse(final String query) 643 throws ParseException { 644 645 return parse(null, URLUtils.parseParameters(query)); 646 } 647 648 649 /** 650 * Parses an authorisation request from the specified URI query string. 651 * 652 * <p>Example URI query string: 653 * 654 * <pre> 655 * response_type=code 656 * &client_id=s6BhdRkqt3 657 * &state=xyz 658 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 659 * </pre> 660 * 661 * @param uri The URI of the authorisation endpoint. May be 662 * {@code null} if the {@link #toHTTPRequest()} method 663 * will not be used. 664 * @param query The URI query string. Must not be {@code null}. 665 * 666 * @return The authorisation request. 667 * 668 * @throws ParseException If the query string couldn't be parsed to an 669 * authorisation request. 670 */ 671 public static AuthorizationRequest parse(final URI uri, final String query) 672 throws ParseException { 673 674 return parse(uri, URLUtils.parseParameters(query)); 675 } 676 677 678 /** 679 * Parses an authorisation request from the specified URI. 680 * 681 * <p>Example URI: 682 * 683 * <pre> 684 * https://server.example.com/authorize? 685 * response_type=code 686 * &client_id=s6BhdRkqt3 687 * &state=xyz 688 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 689 * </pre> 690 * 691 * @param uri The URI. Must not be {@code null}. 692 * 693 * @return The authorisation request. 694 * 695 * @throws ParseException If the URI couldn't be parsed to an 696 * authorisation request. 697 */ 698 public static AuthorizationRequest parse(final URI uri) 699 throws ParseException { 700 701 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getQuery())); 702 } 703 704 705 /** 706 * Parses an authorisation request from the specified HTTP request. 707 * 708 * <p>Example HTTP request (GET): 709 * 710 * <pre> 711 * https://server.example.com/authorize? 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 httpRequest The HTTP request. Must not be {@code null}. 719 * 720 * @return The authorisation request. 721 * 722 * @throws ParseException If the HTTP request couldn't be parsed to an 723 * authorisation request. 724 */ 725 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 726 throws ParseException { 727 728 String query = httpRequest.getQuery(); 729 730 if (query == null) 731 throw new ParseException("Missing URI query string"); 732 733 try { 734 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 735 736 } catch (URISyntaxException e) { 737 738 throw new ParseException(e.getMessage(), e); 739 } 740 } 741}