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.URI; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import net.jcip.annotations.Immutable; 028import net.minidev.json.JSONObject; 029 030import com.nimbusds.jwt.JWT; 031import com.nimbusds.jwt.JWTParser; 032import com.nimbusds.oauth2.sdk.http.HTTPRequest; 033import com.nimbusds.oauth2.sdk.http.HTTPResponse; 034import com.nimbusds.oauth2.sdk.id.Issuer; 035import com.nimbusds.oauth2.sdk.id.State; 036import com.nimbusds.oauth2.sdk.token.AccessToken; 037import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 038import com.nimbusds.oauth2.sdk.util.URIUtils; 039 040 041/** 042 * Authorisation success response. Used to return an authorisation code or 043 * access token at the Authorisation endpoint. 044 * 045 * <p>Example HTTP response with code (code flow): 046 * 047 * <pre> 048 * HTTP/1.1 302 Found 049 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 050 * </pre> 051 * 052 * <p>Example HTTP response with access token (implicit flow): 053 * 054 * <pre> 055 * HTTP/1.1 302 Found 056 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 057 * &state=xyz&token_type=Bearer&expires_in=3600 058 * </pre> 059 * 060 * <p>Related specifications: 061 * 062 * <ul> 063 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2. 064 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 065 * <li>OAuth 2.0 Form Post Response Mode 1.0. 066 * <li>Financial-grade API: JWT Secured Authorization Response Mode for 067 * OAuth 2.0 (JARM). 068 * <li>OAuth 2.0 Authorization Server Issuer Identifier in Authorization 069 * Response (draft-meyerzuselhausen-oauth-iss-auth-resp-01). 070 * </ul> 071 */ 072@Immutable 073public class AuthorizationSuccessResponse 074 extends AuthorizationResponse 075 implements SuccessResponse { 076 077 078 /** 079 * The authorisation code, if requested. 080 */ 081 private final AuthorizationCode code; 082 083 084 /** 085 * The access token, if requested. 086 */ 087 private final AccessToken accessToken; 088 089 090 /** 091 * Creates a new authorisation success response. 092 * 093 * @param redirectURI The base redirection URI. Must not be 094 * {@code null}. 095 * @param code The authorisation code, {@code null} if not 096 * requested. 097 * @param accessToken The access token, {@code null} if not requested. 098 * @param state The state, {@code null} if not specified. 099 * @param rm The response mode, {@code null} if not specified. 100 */ 101 public AuthorizationSuccessResponse(final URI redirectURI, 102 final AuthorizationCode code, 103 final AccessToken accessToken, 104 final State state, 105 final ResponseMode rm) { 106 107 this(redirectURI, code, accessToken, state, null, rm); 108 } 109 110 111 /** 112 * Creates a new authorisation success response. 113 * 114 * @param redirectURI The base redirection URI. Must not be 115 * {@code null}. 116 * @param code The authorisation code, {@code null} if not 117 * requested. 118 * @param accessToken The access token, {@code null} if not requested. 119 * @param state The state, {@code null} if not specified. 120 * @param issuer The issuer, {@code null} if not specified. 121 * @param rm The response mode, {@code null} if not specified. 122 */ 123 public AuthorizationSuccessResponse(final URI redirectURI, 124 final AuthorizationCode code, 125 final AccessToken accessToken, 126 final State state, 127 final Issuer issuer, 128 final ResponseMode rm) { 129 130 super(redirectURI, state, issuer, rm); 131 this.code = code; 132 this.accessToken = accessToken; 133 } 134 135 136 /** 137 * Creates a new JSON Web Token (JWT) secured authorisation success 138 * response. 139 * 140 * @param redirectURI The base redirection URI. Must not be 141 * {@code null}. 142 * @param jwtResponse The JWT-secured response. Must not be 143 * {@code null}. 144 * @param rm The response mode, {@code null} if not specified. 145 */ 146 public AuthorizationSuccessResponse(final URI redirectURI, 147 final JWT jwtResponse, 148 final ResponseMode rm) { 149 150 super(redirectURI, jwtResponse, rm); 151 code = null; 152 accessToken = null; 153 } 154 155 156 @Override 157 public boolean indicatesSuccess() { 158 159 return true; 160 } 161 162 163 /** 164 * Returns the implied response type. 165 * 166 * @return The implied response type. 167 */ 168 public ResponseType impliedResponseType() { 169 170 ResponseType rt = new ResponseType(); 171 172 if (code != null) 173 rt.add(ResponseType.Value.CODE); 174 175 if (accessToken != null) 176 rt.add(ResponseType.Value.TOKEN); 177 178 return rt; 179 } 180 181 182 @Override 183 public ResponseMode impliedResponseMode() { 184 185 if (getResponseMode() != null) { 186 return getResponseMode(); 187 } else { 188 if (getJWTResponse() != null) { 189 // JARM 190 return ResponseMode.JWT; 191 } else if (accessToken != null) { 192 return ResponseMode.FRAGMENT; 193 } else { 194 return ResponseMode.QUERY; 195 } 196 } 197 } 198 199 200 /** 201 * Gets the authorisation code. 202 * 203 * @return The authorisation code, {@code null} if not requested. 204 */ 205 public AuthorizationCode getAuthorizationCode() { 206 207 return code; 208 } 209 210 211 /** 212 * Gets the access token. 213 * 214 * @return The access token, {@code null} if not requested. 215 */ 216 public AccessToken getAccessToken() { 217 218 return accessToken; 219 } 220 221 222 @Override 223 public Map<String,List<String>> toParameters() { 224 225 Map<String,List<String>> params = new HashMap<>(); 226 227 if (getJWTResponse() != null) { 228 // JARM, no other top-level parameters 229 params.put("response", Collections.singletonList(getJWTResponse().serialize())); 230 return params; 231 } 232 233 if (code != null) 234 params.put("code", Collections.singletonList(code.getValue())); 235 236 if (accessToken != null) { 237 238 for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) { 239 240 params.put(entry.getKey(), Collections.singletonList(entry.getValue().toString())); 241 } 242 } 243 244 if (getState() != null) 245 params.put("state", Collections.singletonList(getState().getValue())); 246 247 if (getIssuer() != null) 248 params.put("iss", Collections.singletonList(getIssuer().getValue())); 249 250 return params; 251 } 252 253 254 /** 255 * Parses an authorisation success response. 256 * 257 * @param redirectURI The base redirection URI. Must not be 258 * {@code null}. 259 * @param params The response parameters to parse. Must not be 260 * {@code null}. 261 * 262 * @return The authorisation success response. 263 * 264 * @throws ParseException If the parameters couldn't be parsed to an 265 * authorisation success response. 266 */ 267 public static AuthorizationSuccessResponse parse(final URI redirectURI, 268 final Map<String,List<String>> params) 269 throws ParseException { 270 271 // JARM, ignore other top level params 272 String responseString = MultivaluedMapUtils.getFirstValue(params, "response"); 273 if (responseString != null) { 274 JWT jwtResponse; 275 try { 276 jwtResponse = JWTParser.parse(responseString); 277 } catch (java.text.ParseException e) { 278 throw new ParseException("Invalid JWT response: " + e.getMessage(), e); 279 } 280 281 return new AuthorizationSuccessResponse(redirectURI, jwtResponse, ResponseMode.JWT); 282 } 283 284 // Parse code parameter 285 AuthorizationCode code = null; 286 287 if (params.get("code") != null) { 288 code = new AuthorizationCode(MultivaluedMapUtils.getFirstValue(params, "code")); 289 } 290 291 // Parse access_token parameters 292 AccessToken accessToken = null; 293 294 if (params.get("access_token") != null) { 295 296 JSONObject jsonObject = new JSONObject(); 297 jsonObject.putAll(MultivaluedMapUtils.toSingleValuedMap(params)); 298 accessToken = AccessToken.parse(jsonObject); 299 } 300 301 // Parse optional state parameter 302 State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state")); 303 304 // Parse optional issuer, draft-meyerzuselhausen-oauth-iss-auth-resp-01 305 Issuer issuer = Issuer.parse(MultivaluedMapUtils.getFirstValue(params, "iss")); 306 307 return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state, issuer,null); 308 } 309 310 311 /** 312 * Parses an authorisation success 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 * <p>Example URI: 322 * 323 * <pre> 324 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 325 * </pre> 326 * 327 * @param uri The URI to parse. Can be absolute or relative, with a 328 * fragment or query string containing the authorisation 329 * response parameters. Must not be {@code null}. 330 * 331 * @return The authorisation success response. 332 * 333 * @throws ParseException If the redirection URI couldn't be parsed to 334 * an authorisation success response. 335 */ 336 public static AuthorizationSuccessResponse parse(final URI uri) 337 throws ParseException { 338 339 return parse(URIUtils.getBaseURI(uri), parseResponseParameters(uri)); 340 } 341 342 343 /** 344 * Parses an authorisation success response from the specified initial 345 * HTTP 302 redirect response generated at the authorisation endpoint. 346 * 347 * <p>Example HTTP response: 348 * 349 * <pre> 350 * HTTP/1.1 302 Found 351 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 352 * </pre> 353 * 354 * @see #parse(HTTPRequest) 355 * 356 * @param httpResponse The HTTP response to parse. Must not be 357 * {@code null}. 358 * 359 * @return The authorisation success response. 360 * 361 * @throws ParseException If the HTTP response couldn't be parsed to an 362 * authorisation success response. 363 */ 364 public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse) 365 throws ParseException { 366 367 URI location = httpResponse.getLocation(); 368 369 if (location == null) { 370 throw new ParseException("Missing redirection URL / HTTP Location header"); 371 } 372 373 return parse(location); 374 } 375 376 377 /** 378 * Parses an authorisation success response from the specified HTTP 379 * request at the client redirection (callback) URI. Applies to 380 * {@code query}, {@code fragment} and {@code form_post} response 381 * 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 * @return The authorisation success response. 396 * 397 * @throws ParseException If the HTTP request couldn't be parsed to an 398 * authorisation success response. 399 */ 400 public static AuthorizationSuccessResponse parse(final HTTPRequest httpRequest) 401 throws ParseException { 402 403 return parse(httpRequest.getURI(), parseResponseParameters(httpRequest)); 404 } 405}