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