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