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