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