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