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