001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.util.Map; 008 009import org.apache.commons.lang3.StringUtils; 010 011import com.nimbusds.oauth2.sdk.id.State; 012import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 013import com.nimbusds.oauth2.sdk.http.HTTPRequest; 014import com.nimbusds.oauth2.sdk.http.HTTPResponse; 015import com.nimbusds.oauth2.sdk.util.URIUtils; 016import com.nimbusds.oauth2.sdk.util.URLUtils; 017 018 019/** 020 * The base abstract class for authorisation success and error responses. 021 * 022 * <p>Related specifications: 023 * 024 * <ul> 025 * <li>OAuth 2.0 (RFC 6749), section 3.1. 026 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 027 * <li>OAuth 2.0 Form Post Response Mode 1.0. 028 * </ul> 029 */ 030public abstract class AuthorizationResponse implements Response { 031 032 033 /** 034 * The base redirection URI. 035 */ 036 private final URI redirectURI; 037 038 039 /** 040 * The optional state parameter to be echoed back to the client. 041 */ 042 private final State state; 043 044 045 /** 046 * The optional explicit response mode. 047 */ 048 private final ResponseMode rm; 049 050 051 /** 052 * Creates a new authorisation response. 053 * 054 * @param redirectURI The base redirection URI. Must not be 055 * {@code null}. 056 * @param state The state, {@code null} if not requested. 057 * @param rm The response mode, {@code null} if not specified. 058 */ 059 protected AuthorizationResponse(final URI redirectURI, final State state, final ResponseMode rm) { 060 061 if (redirectURI == null) { 062 throw new IllegalArgumentException("The redirection URI must not be null"); 063 } 064 065 this.redirectURI = redirectURI; 066 067 this.state = state; 068 069 this.rm = rm; 070 } 071 072 073 /** 074 * Returns the base redirection URI. 075 * 076 * @return The base redirection URI (without the appended error 077 * response parameters). 078 */ 079 public URI getRedirectionURI() { 080 081 return redirectURI; 082 } 083 084 085 /** 086 * Returns the optional state. 087 * 088 * @return The state, {@code null} if not requested. 089 */ 090 public State getState() { 091 092 return state; 093 } 094 095 096 /** 097 * Returns the optional explicit response mode. 098 * 099 * @return The response mode, {@code null} if not specified. 100 */ 101 public ResponseMode getResponseMode() { 102 103 return rm; 104 } 105 106 107 /** 108 * Determines the implied response mode. 109 * 110 * @return The implied response mode. 111 */ 112 public abstract ResponseMode impliedResponseMode(); 113 114 115 /** 116 * Returns the parameters of this authorisation response. 117 * 118 * <p>Example parameters (authorisation success): 119 * 120 * <pre> 121 * access_token = 2YotnFZFEjr1zCsicMWpAA 122 * state = xyz 123 * token_type = example 124 * expires_in = 3600 125 * </pre> 126 * 127 * @return The parameters as a map. 128 * 129 * @throws SerializeException If this response couldn't be serialised 130 * to a parameters map. 131 */ 132 public abstract Map<String,String> toParameters() 133 throws SerializeException; 134 135 136 /** 137 * Returns a URI representation (redirection URI + fragment / query 138 * string) of this authorisation response. 139 * 140 * <p>Example URI: 141 * 142 * <pre> 143 * http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 144 * &state=xyz 145 * &token_type=example 146 * &expires_in=3600 147 * </pre> 148 * 149 * @return A URI representation of this authorisation response. 150 * 151 * @throws SerializeException If this response couldn't be serialised 152 * to a URI. 153 */ 154 public URI toURI() 155 throws SerializeException { 156 157 final ResponseMode rm = impliedResponseMode(); 158 159 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 160 161 if (rm.equals(ResponseMode.QUERY)) { 162 if (StringUtils.isBlank(getRedirectionURI().getQuery())) { 163 sb.append('?'); 164 } else { 165 // The original redirect_uri may contain query params, 166 // see http://tools.ietf.org/html/rfc6749#section-3.1.2 167 sb.append('&'); 168 } 169 } else if (rm.equals(ResponseMode.FRAGMENT)) { 170 sb.append('#'); 171 } else { 172 throw new SerializeException("The (implied) response mode must be query or fragment"); 173 } 174 175 sb.append(URLUtils.serializeParameters(toParameters())); 176 177 try { 178 return new URI(sb.toString()); 179 180 } catch (URISyntaxException e) { 181 182 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 183 } 184 } 185 186 187 /** 188 * Returns an HTTP response for this authorisation response. Applies to 189 * the {@code query} or {@code fragment} response mode using HTTP 302 190 * redirection. 191 * 192 * <p>Example HTTP response (authorisation success): 193 * 194 * <pre> 195 * HTTP/1.1 302 Found 196 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 197 * &state=xyz 198 * &token_type=example 199 * &expires_in=3600 200 * </pre> 201 * 202 * @see #toHTTPRequest() 203 * 204 * @return An HTTP response for this authorisation response. 205 * 206 * @throws SerializeException If the response couldn't be serialised to 207 * an HTTP response. 208 */ 209 @Override 210 public HTTPResponse toHTTPResponse() 211 throws SerializeException { 212 213 if (ResponseMode.FORM_POST.equals(rm)) { 214 throw new SerializeException("The response mode must not be form_post"); 215 } 216 217 HTTPResponse response= new HTTPResponse(HTTPResponse.SC_FOUND); 218 response.setLocation(toURI()); 219 return response; 220 } 221 222 223 /** 224 * Returns an HTTP request for this authorisation response. Applies to 225 * the {@code form_post} response mode. 226 * 227 * <p>Example HTTP request (authorisation success): 228 * 229 * <pre> 230 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 231 * Host: client.example.com 232 * </pre> 233 * 234 * @see #toHTTPResponse() 235 * 236 * @return An HTTP request for this authorisation response. 237 * 238 * @throws SerializeException If the response couldn't be serialised to 239 * an HTTP request. 240 */ 241 public HTTPRequest toHTTPRequest() 242 throws SerializeException { 243 244 if (! ResponseMode.FORM_POST.equals(rm)) { 245 throw new SerializeException("The response mode must be form_post"); 246 } 247 248 // Use HTTP POST 249 HTTPRequest request; 250 251 try { 252 request = new HTTPRequest(HTTPRequest.Method.POST, redirectURI.toURL()); 253 254 } catch (MalformedURLException e) { 255 throw new SerializeException(e.getMessage(), e); 256 } 257 258 request.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 259 request.setQuery(URLUtils.serializeParameters(toParameters())); 260 return request; 261 } 262 263 264 /** 265 * Parses an authorisation response. 266 * 267 * @param redirectURI The base redirection URI. Must not be 268 * {@code null}. 269 * @param params The response parameters to parse. Must not be 270 * {@code null}. 271 * 272 * @return The authorisation success or error response. 273 * 274 * @throws ParseException If the parameters couldn't be parsed to an 275 * authorisation success or error response. 276 */ 277 public static AuthorizationResponse parse(final URI redirectURI, final Map<String,String> params) 278 throws ParseException { 279 280 if (StringUtils.isNotBlank(params.get("error"))) { 281 return AuthorizationErrorResponse.parse(redirectURI, params); 282 } else { 283 return AuthorizationSuccessResponse.parse(redirectURI, params); 284 } 285 } 286 287 288 /** 289 * Parses an authorisation response. 290 * 291 * <p>Use a relative URI if the host, port and path details are not 292 * known: 293 * 294 * <pre> 295 * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj"); 296 * </pre> 297 * 298 * @param uri The URI to parse. May be absolute or relative, with a 299 * fragment or query string containing the authorisation 300 * response parameters. Must not be {@code null}. 301 * 302 * @return The authorisation success or error response. 303 * 304 * @throws ParseException If no authorisation response parameters were 305 * found in the URL. 306 */ 307 public static AuthorizationResponse parse(final URI uri) 308 throws ParseException { 309 310 Map<String,String> params; 311 312 if (uri.getRawFragment() != null) { 313 params = URLUtils.parseParameters(uri.getRawFragment()); 314 } else if (uri.getQuery() != null) { 315 params = URLUtils.parseParameters(uri.getQuery()); 316 } else { 317 throw new ParseException("Missing URI fragment or query string"); 318 } 319 320 return parse(URIUtils.getBaseURI(uri), params); 321 } 322 323 324 /** 325 * Parses an authorisation response from the specified initial HTTP 302 326 * redirect response output at the authorisation endpoint. 327 * 328 * <p>Example HTTP response (authorisation success): 329 * 330 * <pre> 331 * HTTP/1.1 302 Found 332 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 333 * </pre> 334 * 335 * @see #parse(HTTPRequest) 336 * 337 * @param httpResponse The HTTP response to parse. Must not be 338 * {@code null}. 339 * 340 * @throws ParseException If the HTTP response couldn't be parsed to an 341 * authorisation response. 342 */ 343 public static AuthorizationResponse parse(final HTTPResponse httpResponse) 344 throws ParseException { 345 346 URI location = httpResponse.getLocation(); 347 348 if (location == null) { 349 throw new ParseException("Missing redirection URI / HTTP Location header"); 350 } 351 352 return parse(location); 353 } 354 355 356 /** 357 * Parses an authorisation response from the specified HTTP request at 358 * the client redirection (callback) URI. Applies to the {@code query}, 359 * {@code fragment} and {@code form_post} response modes. 360 * 361 * <p>Example HTTP request (authorisation success): 362 * 363 * <pre> 364 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 365 * Host: client.example.com 366 * </pre> 367 * 368 * @see #parse(HTTPResponse) 369 * 370 * @param httpRequest The HTTP request to parse. Must not be 371 * {@code null}. 372 * 373 * @throws ParseException If the HTTP request couldn't be parsed to an 374 * authorisation response. 375 */ 376 public static AuthorizationResponse parse(final HTTPRequest httpRequest) 377 throws ParseException { 378 379 final URI baseURI; 380 381 try { 382 baseURI = httpRequest.getURL().toURI(); 383 384 } catch (URISyntaxException e) { 385 throw new ParseException(e.getMessage(), e); 386 } 387 388 if (httpRequest.getQuery() != null) { 389 // For query string and form_post response mode 390 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 391 } else if (httpRequest.getFragment() != null) { 392 // For fragment response mode (never available in actual HTTP request from browser) 393 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 394 } else { 395 throw new ParseException("Missing URI fragment, query string or post body"); 396 } 397 } 398}