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