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