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