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