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