001 package com.nimbusds.oauth2.sdk; 002 003 004 import java.net.MalformedURLException; 005 import java.net.URL; 006 import java.util.HashMap; 007 import java.util.Map; 008 009 import net.jcip.annotations.Immutable; 010 011 import net.minidev.json.JSONObject; 012 013 import com.nimbusds.oauth2.sdk.id.State; 014 import com.nimbusds.oauth2.sdk.token.AccessToken; 015 import com.nimbusds.oauth2.sdk.http.HTTPResponse; 016 import com.nimbusds.oauth2.sdk.util.URLUtils; 017 018 019 /** 020 * Authorisation success response. Used to return an authorisation code or 021 * access token at the Authorisation endpoint. This class is immutable. 022 * 023 * <p>Example HTTP response with code (code flow): 024 * 025 * <pre> 026 * HTTP/1.1 302 Found 027 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 028 * </pre> 029 * 030 * <p>Example HTTP response with access token (implicit flow): 031 * 032 * <pre> 033 * HTTP/1.1 302 Found 034 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 035 * &state=xyz&token_type=Bearer&expires_in=3600 036 * </pre> 037 * 038 * <p>Related specifications: 039 * 040 * <ul> 041 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2. 042 * </ul> 043 * 044 * @author Vladimir Dzhuvinov 045 */ 046 @Immutable 047 public class AuthorizationSuccessResponse 048 extends AuthorizationResponse 049 implements SuccessResponse { 050 051 052 /** 053 * The authorisation code, if requested. 054 */ 055 private final AuthorizationCode code; 056 057 058 /** 059 * The access token, if requested. 060 */ 061 private final AccessToken accessToken; 062 063 064 /** 065 * Creates a new authorisation success response in the code flow 066 * (authorisation code grant). 067 * 068 * @param redirectURI The base redirect URI. Must not be {@code null}. 069 * @param code The authorisation code. Must not be {@code null}. 070 * @param state The state, {@code null} if not requested. 071 */ 072 public AuthorizationSuccessResponse(final URL redirectURI, 073 final AuthorizationCode code, 074 final State state) { 075 076 this(redirectURI, code, null, state); 077 078 if (code == null) 079 throw new IllegalArgumentException("The authorization code must not be null"); 080 } 081 082 083 /** 084 * Creates a new authorisation success response in the implicit flow 085 * (implicit grant). 086 * 087 * @param redirectURI The base redirect URI. Must not be {@code null}. 088 * @param accessToken The access token. Must not be {@code null}. 089 * @param state The state, {@code null} if not requested. 090 */ 091 public AuthorizationSuccessResponse(final URL redirectURI, 092 final AccessToken accessToken, 093 final State state) { 094 095 this(redirectURI, null, accessToken, state); 096 097 if (accessToken == null) 098 throw new IllegalArgumentException("The access token must not be null"); 099 } 100 101 102 /** 103 * Creates a new authorisation success response. 104 * 105 * @param redirectURI The base redirect URI. Must not be {@code null}. 106 * @param code The authorisation code, {@code null} if not 107 * requested. 108 * @param accessToken The access token, {@code null} if not requested. 109 * @param state The state, {@code null} if not requested. 110 */ 111 public AuthorizationSuccessResponse(final URL redirectURI, 112 final AuthorizationCode code, 113 final AccessToken accessToken, 114 final State state) { 115 116 super(redirectURI, state); 117 118 this.code = code; 119 120 this.accessToken = accessToken; 121 } 122 123 124 /** 125 * Gets the implied response type. 126 * 127 * @return The implied response type. 128 */ 129 public ResponseType getImpliedResponseType() { 130 131 ResponseType rt = new ResponseType(); 132 133 if (code != null) 134 rt.add(ResponseType.Value.CODE); 135 136 if (accessToken != null) 137 rt.add(ResponseType.Value.TOKEN); 138 139 return rt; 140 } 141 142 143 /** 144 * Gets the authorisation code. 145 * 146 * @return The authorisation code, {@code null} if not requested. 147 */ 148 public AuthorizationCode getAuthorizationCode() { 149 150 return code; 151 } 152 153 154 /** 155 * Gets the access token. 156 * 157 * @return The access token, {@code null} if not requested. 158 */ 159 public AccessToken getAccessToken() { 160 161 return accessToken; 162 } 163 164 165 @Override 166 public Map<String,String> toParameters() 167 throws SerializeException { 168 169 Map<String,String> params = new HashMap<String,String>(); 170 171 if (code != null) 172 params.put("code", code.getValue()); 173 174 if (accessToken != null) { 175 176 for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) { 177 178 params.put(entry.getKey(), entry.getValue().toString()); 179 } 180 } 181 182 if (getState() != null) 183 params.put("state", getState().getValue()); 184 185 return params; 186 } 187 188 189 @Override 190 public URL toURI() 191 throws SerializeException { 192 193 StringBuilder sb = new StringBuilder(getRedirectURI().toString()); 194 195 // Fragment or query string? 196 if (accessToken != null) 197 sb.append('#'); 198 else 199 sb.append('?'); 200 201 202 sb.append(URLUtils.serializeParameters(toParameters())); 203 204 205 try { 206 return new URL(sb.toString()); 207 208 } catch (MalformedURLException e) { 209 210 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 211 } 212 } 213 214 215 /** 216 * Parses an authorisation success response. 217 * 218 * @param redirectURI The base redirect URI. Must not be {@code null}. 219 * @param params The response parameters to parse. Must not be 220 * {@code null}. 221 * 222 * @return The authorisation success response. 223 * 224 * @throws ParseException If the parameters couldn't be parsed to an 225 * authorisation success response. 226 */ 227 public static AuthorizationSuccessResponse parse(final URL redirectURI, 228 final Map<String,String> params) 229 throws ParseException { 230 231 // Parse code parameter 232 233 AuthorizationCode code = null; 234 235 if (params.get("code") != null) 236 code = new AuthorizationCode(params.get("code")); 237 238 239 // Parse access_token parameters 240 241 AccessToken accessToken = null; 242 243 if (params.get("access_token") != null) { 244 245 JSONObject jsonObject = new JSONObject(); 246 247 jsonObject.putAll(params); 248 249 accessToken = AccessToken.parse(jsonObject); 250 } 251 252 253 // Parse optional state parameter 254 State state = State.parse(params.get("state")); 255 256 257 return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state); 258 } 259 260 261 /** 262 * Parses an authorisation success response. 263 * 264 * <p>Example URI: 265 * 266 * <pre> 267 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 268 * </pre> 269 * 270 * @param uri The URI to parse. Can be absolute or relative, with a 271 * fragment or query string containing the authorisation 272 * response parameters. Must not be {@code null}. 273 * 274 * @return The authorisation success response. 275 * 276 * @throws ParseException If the redirect URI couldn't be parsed to an 277 * authorisation success response. 278 */ 279 public static AuthorizationSuccessResponse parse(final URL uri) 280 throws ParseException { 281 282 String paramString = null; 283 284 if (uri.getQuery() != null) 285 paramString = uri.getQuery(); 286 287 else if (uri.getRef() != null) 288 paramString = uri.getRef(); 289 290 else 291 throw new ParseException("Missing authorization response parameters"); 292 293 Map<String,String> params = URLUtils.parseParameters(paramString); 294 295 if (params == null) 296 throw new ParseException("Missing or invalid authorization response parameters"); 297 298 return parse(URLUtils.getBaseURL(uri), params); 299 } 300 301 302 /** 303 * Parses an authorisation success response. 304 * 305 * <p>Example HTTP response: 306 * 307 * <pre> 308 * HTTP/1.1 302 Found 309 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 310 * </pre> 311 * 312 * @param httpResponse The HTTP response to parse. Must not be 313 * {@code null}. 314 * 315 * @return The authorisation success response. 316 * 317 * @throws ParseException If the HTTP response couldn't be parsed to an 318 * authorisation success response. 319 */ 320 public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse) 321 throws ParseException { 322 323 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 324 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 325 httpResponse.getStatusCode()); 326 327 URL location = httpResponse.getLocation(); 328 329 if (location == null) 330 throw new ParseException("Missing redirect URL / HTTP Location header"); 331 332 return parse(location); 333 } 334 }