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