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 * This class is immutable. 023 * 024 * <p>Extending classes may define additional request parameters as well as 025 * enforce tighter requirements on the base parameters. 026 * 027 * <p>Example HTTP request: 028 * 029 * <pre> 030 * https://server.example.com/authorize? 031 * response_type=code 032 * &client_id=s6BhdRkqt3 033 * &state=xyz 034 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 035 * </pre> 036 * 037 * <p>Related specifications: 038 * 039 * <ul> 040 * <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1. 041 * </ul> 042 * 043 * @author Vladimir Dzhuvinov 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 URL 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 * Creates a new minimal authorisation request. 082 * 083 * @param uri The URI of the authorisation endpoint. May be 084 * {@code null} if the {@link #toHTTPRequest()} 085 * method will not be used. 086 * @param rt The response type. Corresponds to the 087 * {@code response_type} parameter. Must not be 088 * {@code null}. 089 * @param clientID The client identifier. Corresponds to the 090 * {@code client_id} parameter. Must not be 091 * {@code null}. 092 */ 093 public AuthorizationRequest(final URL uri, 094 final ResponseType rt, 095 final ClientID clientID) { 096 097 this(uri, rt, clientID, null, null, null); 098 } 099 100 101 /** 102 * Creates a new authorisation request. 103 * 104 * @param uri The URI of the authorisation endpoint. May be 105 * {@code null} if the {@link #toHTTPRequest()} 106 * method will not be used. 107 * @param rt The response type. Corresponds to the 108 * {@code response_type} parameter. Must not be 109 * {@code null}. 110 * @param clientID The client identifier. Corresponds to the 111 * {@code client_id} parameter. Must not be 112 * {@code null}. 113 * @param redirectURI The redirection URI. Corresponds to the optional 114 * {@code redirect_uri} parameter. {@code null} if 115 * not specified. 116 * @param scope The request scope. Corresponds to the optional 117 * {@code scope} parameter. {@code null} if not 118 * specified. 119 * @param state The state. Corresponds to the recommended 120 * {@code state} parameter. {@code null} if not 121 * specified. 122 */ 123 public AuthorizationRequest(final URL uri, 124 final ResponseType rt, 125 final ClientID clientID, 126 final URL redirectURI, 127 final Scope scope, 128 final State state) { 129 130 super(uri); 131 132 if (rt == null) 133 throw new IllegalArgumentException("The response type must not be null"); 134 135 this.rt = rt; 136 137 138 if (clientID == null) 139 throw new IllegalArgumentException("The client ID must not be null"); 140 141 this.clientID = clientID; 142 143 144 this.redirectURI = redirectURI; 145 this.scope = scope; 146 this.state = state; 147 } 148 149 150 /** 151 * Gets the response type. Corresponds to the {@code response_type} 152 * parameter. 153 * 154 * @return The response type. 155 */ 156 public ResponseType getResponseType() { 157 158 return rt; 159 } 160 161 162 /** 163 * Gets the client identifier. Corresponds to the {@code client_id} 164 * parameter. 165 * 166 * @return The client identifier. 167 */ 168 public ClientID getClientID() { 169 170 return clientID; 171 } 172 173 174 /** 175 * Gets the redirection URI. Corresponds to the optional 176 * {@code redirection_uri} parameter. 177 * 178 * @return The redirection URI, {@code null} if not specified. 179 */ 180 public URL getRedirectionURI() { 181 182 return redirectURI; 183 } 184 185 186 /** 187 * Gets the scope. Corresponds to the optional {@code scope} parameter. 188 * 189 * @return The scope, {@code null} if not specified. 190 */ 191 public Scope getScope() { 192 193 return scope; 194 } 195 196 197 /** 198 * Gets the state. Corresponds to the recommended {@code state} 199 * parameter. 200 * 201 * @return The state, {@code null} if not specified. 202 */ 203 public State getState() { 204 205 return state; 206 } 207 208 209 /** 210 * Returns the parameters for this authorisation request. 211 * 212 * <p>Example parameters: 213 * 214 * <pre> 215 * response_type = code 216 * client_id = s6BhdRkqt3 217 * state = xyz 218 * redirect_uri = https://client.example.com/cb 219 * </pre> 220 * 221 * @return The parameters. 222 * 223 * @throws SerializeException If this authorisation request couldn't be 224 * serialised to an parameters map. 225 */ 226 public Map<String,String> toParameters() 227 throws SerializeException { 228 229 Map <String,String> params = new LinkedHashMap<String,String>(); 230 231 params.put("response_type", rt.toString()); 232 params.put("client_id", clientID.getValue()); 233 234 if (redirectURI != null) 235 params.put("redirect_uri", redirectURI.toString()); 236 237 if (scope != null) 238 params.put("scope", scope.toString()); 239 240 if (state != null) 241 params.put("state", state.getValue()); 242 243 return params; 244 } 245 246 247 /** 248 * Returns the URL query string for this authorisation request. 249 * 250 * <p>Note that the '?' character preceding the query string in an URL 251 * is not included in the returned string. 252 * 253 * <p>Example URL query string: 254 * 255 * <pre> 256 * response_type=code 257 * &client_id=s6BhdRkqt3 258 * &state=xyz 259 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 260 * </pre> 261 * 262 * @return The URL query string. 263 * 264 * @throws SerializeException If this authorisation request couldn't be 265 * serialised to an URL query string. 266 */ 267 public String toQueryString() 268 throws SerializeException { 269 270 return URLUtils.serializeParameters(toParameters()); 271 } 272 273 274 /** 275 * Returns the matching HTTP request. 276 * 277 * @param method The HTTP request method which can be GET or POST. Must 278 * not be {@code null}. 279 * 280 * @return The HTTP request. 281 * 282 * @throws SerializeException If the authorisation request message 283 * couldn't be serialised to an HTTP 284 * request. 285 */ 286 public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) 287 throws SerializeException { 288 289 if (getURI() == null) 290 throw new SerializeException("The endpoint URI is not specified"); 291 292 HTTPRequest httpRequest; 293 294 if (method.equals(HTTPRequest.Method.GET)) { 295 296 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, getURI()); 297 298 } else if (method.equals(HTTPRequest.Method.POST)) { 299 300 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getURI()); 301 302 } else { 303 304 throw new IllegalArgumentException("The HTTP request method must be GET or POST"); 305 } 306 307 httpRequest.setQuery(toQueryString()); 308 309 return httpRequest; 310 } 311 312 313 @Override 314 public HTTPRequest toHTTPRequest() 315 throws SerializeException { 316 317 return toHTTPRequest(HTTPRequest.Method.GET); 318 } 319 320 321 /** 322 * Parses an authorisation request from the specified parameters. 323 * 324 * <p>Example parameters: 325 * 326 * <pre> 327 * response_type = code 328 * client_id = s6BhdRkqt3 329 * state = xyz 330 * redirect_uri = https://client.example.com/cb 331 * </pre> 332 * 333 * @param uri The URI of the authorisation endpoint. May be 334 * {@code null} if the {@link #toHTTPRequest()} method 335 * will not be used. 336 * @param params The parameters. Must not be {@code null}. 337 * 338 * @return The authorisation request. 339 * 340 * @throws ParseException If the parameters couldn't be parsed to an 341 * authorisation request. 342 */ 343 public static AuthorizationRequest parse(final URL uri, final Map<String,String> params) 344 throws ParseException { 345 346 // Parse mandatory client ID first 347 String v = params.get("client_id"); 348 349 if (StringUtils.isBlank(v)) 350 throw new ParseException("Missing \"client_id\" parameter", 351 OAuth2Error.INVALID_REQUEST); 352 353 ClientID clientID = new ClientID(v); 354 355 356 // Parse optional redirect URI second 357 v = params.get("redirect_uri"); 358 359 URL redirectURI = null; 360 361 if (StringUtils.isNotBlank(v)) { 362 363 try { 364 redirectURI = new URL(v); 365 366 } catch (MalformedURLException e) { 367 368 throw new ParseException("Invalid \"redirect_uri\" parameter: " + e.getMessage(), 369 OAuth2Error.INVALID_REQUEST, e); 370 } 371 } 372 373 374 // Parse optional state third 375 State state = State.parse(params.get("state")); 376 377 378 // Parse mandatory response type 379 v = params.get("response_type"); 380 381 ResponseType rt; 382 383 try { 384 rt = ResponseType.parse(v); 385 386 } catch (ParseException e) { 387 388 throw new ParseException(e.getMessage(), 389 OAuth2Error.UNSUPPORTED_RESPONSE_TYPE, 390 redirectURI, state, e); 391 } 392 393 394 // Parse optional scope 395 v = params.get("scope"); 396 397 Scope scope = null; 398 399 if (StringUtils.isNotBlank(v)) 400 scope = Scope.parse(v); 401 402 403 return new AuthorizationRequest(uri, rt, clientID, redirectURI, scope, state); 404 405 } 406 407 408 /** 409 * Parses an authorisation request from the specified URL query string. 410 * 411 * <p>Example URL query string: 412 * 413 * <pre> 414 * response_type=code 415 * &client_id=s6BhdRkqt3 416 * &state=xyz 417 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 418 * </pre> 419 * 420 * @param uri The URI of the authorisation endpoint. May be 421 * {@code null} if the {@link #toHTTPRequest()} method 422 * will not be used. 423 * @param query The URL query string. Must not be {@code null}. 424 * 425 * @return The authorisation request. 426 * 427 * @throws ParseException If the query string couldn't be parsed to an 428 * authorisation request. 429 */ 430 public static AuthorizationRequest parse(final URL uri, final String query) 431 throws ParseException { 432 433 return parse(uri, URLUtils.parseParameters(query)); 434 } 435 436 437 /** 438 * Parses an authorisation request from the specified HTTP request. 439 * 440 * <p>Example HTTP request (GET): 441 * 442 * <pre> 443 * https://server.example.com/authorize? 444 * response_type=code 445 * &client_id=s6BhdRkqt3 446 * &state=xyz 447 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 448 * </pre> 449 * 450 * @param httpRequest The HTTP request. Must not be {@code null}. 451 * 452 * @return The authorisation request. 453 * 454 * @throws ParseException If the HTTP request couldn't be parsed to an 455 * authorisation request. 456 */ 457 public static AuthorizationRequest parse(final HTTPRequest httpRequest) 458 throws ParseException { 459 460 String query = httpRequest.getQuery(); 461 462 if (query == null) 463 throw new ParseException("Missing URL query string"); 464 465 return parse(URLUtils.getBaseURL(httpRequest.getURL()), query); 466 } 467}