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 sb.append('?'); 163 } else if (rm.equals(ResponseMode.FRAGMENT)) { 164 sb.append('#'); 165 } else { 166 throw new SerializeException("The (implied) response mode must be query or fragment"); 167 } 168 169 sb.append(URLUtils.serializeParameters(toParameters())); 170 171 try { 172 return new URI(sb.toString()); 173 174 } catch (URISyntaxException e) { 175 176 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 177 } 178 } 179 180 181 /** 182 * Returns an HTTP response for this authorisation response. Applies to 183 * the {@code query} or {@code fragment} response mode using HTTP 302 184 * redirection. 185 * 186 * <p>Example HTTP response (authorisation success): 187 * 188 * <pre> 189 * HTTP/1.1 302 Found 190 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 191 * &state=xyz 192 * &token_type=example 193 * &expires_in=3600 194 * </pre> 195 * 196 * @see #toHTTPRequest() 197 * 198 * @return An HTTP response for this authorisation response. 199 * 200 * @throws SerializeException If the response couldn't be serialised to 201 * an HTTP response. 202 */ 203 @Override 204 public HTTPResponse toHTTPResponse() 205 throws SerializeException { 206 207 if (ResponseMode.FORM_POST.equals(rm)) { 208 throw new SerializeException("The response mode must not be form_post"); 209 } 210 211 HTTPResponse response= new HTTPResponse(HTTPResponse.SC_FOUND); 212 response.setLocation(toURI()); 213 return response; 214 } 215 216 217 /** 218 * Returns an HTTP request for this authorisation response. Applies to 219 * the {@code form_post} response mode. 220 * 221 * <p>Example HTTP request (authorisation success): 222 * 223 * <pre> 224 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 225 * Host: client.example.com 226 * </pre> 227 * 228 * @see #toHTTPResponse() 229 * 230 * @return An HTTP request for this authorisation response. 231 * 232 * @throws SerializeException If the response couldn't be serialised to 233 * an HTTP request. 234 */ 235 public HTTPRequest toHTTPRequest() 236 throws SerializeException { 237 238 if (! ResponseMode.FORM_POST.equals(rm)) { 239 throw new SerializeException("The response mode must be form_post"); 240 } 241 242 // Use HTTP POST 243 HTTPRequest request; 244 245 try { 246 request = new HTTPRequest(HTTPRequest.Method.POST, redirectURI.toURL()); 247 248 } catch (MalformedURLException e) { 249 throw new SerializeException(e.getMessage(), e); 250 } 251 252 request.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 253 request.setQuery(URLUtils.serializeParameters(toParameters())); 254 return request; 255 } 256 257 258 /** 259 * Parses an authorisation response. 260 * 261 * @param redirectURI The base redirection URI. Must not be 262 * {@code null}. 263 * @param params The response parameters to parse. Must not be 264 * {@code null}. 265 * 266 * @return The authorisation success or error response. 267 * 268 * @throws ParseException If the parameters couldn't be parsed to an 269 * authorisation success or error response. 270 */ 271 public static AuthorizationResponse parse(final URI redirectURI, final Map<String,String> params) 272 throws ParseException { 273 274 if (StringUtils.isNotBlank(params.get("error"))) { 275 return AuthorizationErrorResponse.parse(redirectURI, params); 276 } else { 277 return AuthorizationSuccessResponse.parse(redirectURI, params); 278 } 279 } 280 281 282 /** 283 * Parses an authorisation response. 284 * 285 * <p>Use a relative URI if the host, port and path details are not 286 * known: 287 * 288 * <pre> 289 * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj"); 290 * </pre> 291 * 292 * @param uri The URI to parse. May be absolute or relative, with a 293 * fragment or query string containing the authorisation 294 * response parameters. Must not be {@code null}. 295 * 296 * @return The authorisation success or error response. 297 * 298 * @throws ParseException If no authorisation response parameters were 299 * found in the URL. 300 */ 301 public static AuthorizationResponse parse(final URI uri) 302 throws ParseException { 303 304 Map<String,String> params; 305 306 if (uri.getRawFragment() != null) { 307 params = URLUtils.parseParameters(uri.getRawFragment()); 308 } else if (uri.getQuery() != null) { 309 params = URLUtils.parseParameters(uri.getQuery()); 310 } else { 311 throw new ParseException("Missing URI fragment or query string"); 312 } 313 314 return parse(URIUtils.getBaseURI(uri), params); 315 } 316 317 318 /** 319 * Parses an authorisation response from the specified initial HTTP 302 320 * redirect response output at the authorisation endpoint. 321 * 322 * <p>Example HTTP response (authorisation success): 323 * 324 * <pre> 325 * HTTP/1.1 302 Found 326 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 327 * </pre> 328 * 329 * @see #parse(HTTPRequest) 330 * 331 * @param httpResponse The HTTP response to parse. Must not be 332 * {@code null}. 333 * 334 * @throws ParseException If the HTTP response couldn't be parsed to an 335 * authorisation response. 336 */ 337 public static AuthorizationResponse parse(final HTTPResponse httpResponse) 338 throws ParseException { 339 340 URI location = httpResponse.getLocation(); 341 342 if (location == null) { 343 throw new ParseException("Missing redirection URI / HTTP Location header"); 344 } 345 346 return parse(location); 347 } 348 349 350 /** 351 * Parses an authorisation response from the specified HTTP request at 352 * the client redirection (callback) URI. Applies to the {@code query}, 353 * {@code fragment} and {@code form_post} response modes. 354 * 355 * <p>Example HTTP request (authorisation success): 356 * 357 * <pre> 358 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 359 * Host: client.example.com 360 * </pre> 361 * 362 * @see #parse(HTTPResponse) 363 * 364 * @param httpRequest The HTTP request to parse. Must not be 365 * {@code null}. 366 * 367 * @throws ParseException If the HTTP request couldn't be parsed to an 368 * authorisation response. 369 */ 370 public static AuthorizationResponse parse(final HTTPRequest httpRequest) 371 throws ParseException { 372 373 final URI baseURI; 374 375 try { 376 baseURI = httpRequest.getURL().toURI(); 377 378 } catch (URISyntaxException e) { 379 throw new ParseException(e.getMessage(), e); 380 } 381 382 if (httpRequest.getQuery() != null) { 383 // For query string and form_post response mode 384 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 385 } else if (httpRequest.getFragment() != null) { 386 // For fragment response mode (never available in actual HTTP request from browser) 387 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 388 } else { 389 throw new ParseException("Missing URI fragment, query string or post body"); 390 } 391 } 392}