001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URL; 006import java.util.HashMap; 007import java.util.Map; 008 009import net.jcip.annotations.Immutable; 010 011import net.minidev.json.JSONObject; 012 013import com.nimbusds.oauth2.sdk.id.State; 014import com.nimbusds.oauth2.sdk.token.AccessToken; 015import com.nimbusds.oauth2.sdk.http.HTTPResponse; 016import 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 047public 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 redirection URI. Must not be 069 * {@code null}. 070 * @param code The authorisation code. Must not be {@code null}. 071 * @param state The state, {@code null} if not requested. 072 */ 073 public AuthorizationSuccessResponse(final URL redirectURI, 074 final AuthorizationCode code, 075 final State state) { 076 077 this(redirectURI, code, null, state); 078 079 if (code == null) 080 throw new IllegalArgumentException("The authorization code must not be null"); 081 } 082 083 084 /** 085 * Creates a new authorisation success response in the implicit flow 086 * (implicit grant). 087 * 088 * @param redirectURI The base redirection URI. Must not be 089 * {@code null}. 090 * @param accessToken The access token. Must not be {@code null}. 091 * @param state The state, {@code null} if not requested. 092 */ 093 public AuthorizationSuccessResponse(final URL redirectURI, 094 final AccessToken accessToken, 095 final State state) { 096 097 this(redirectURI, null, accessToken, state); 098 099 if (accessToken == null) 100 throw new IllegalArgumentException("The access token must not be null"); 101 } 102 103 104 /** 105 * Creates a new authorisation success response. 106 * 107 * @param redirectURI The base redirection URI. Must not be 108 * {@code null}. 109 * @param code The authorisation code, {@code null} if not 110 * requested. 111 * @param accessToken The access token, {@code null} if not requested. 112 * @param state The state, {@code null} if not requested. 113 */ 114 public AuthorizationSuccessResponse(final URL redirectURI, 115 final AuthorizationCode code, 116 final AccessToken accessToken, 117 final State state) { 118 119 super(redirectURI, state); 120 121 this.code = code; 122 123 this.accessToken = accessToken; 124 } 125 126 127 /** 128 * Gets the implied response type. 129 * 130 * @return The implied response type. 131 */ 132 public ResponseType getImpliedResponseType() { 133 134 ResponseType rt = new ResponseType(); 135 136 if (code != null) 137 rt.add(ResponseType.Value.CODE); 138 139 if (accessToken != null) 140 rt.add(ResponseType.Value.TOKEN); 141 142 return rt; 143 } 144 145 146 /** 147 * Gets the authorisation code. 148 * 149 * @return The authorisation code, {@code null} if not requested. 150 */ 151 public AuthorizationCode getAuthorizationCode() { 152 153 return code; 154 } 155 156 157 /** 158 * Gets the access token. 159 * 160 * @return The access token, {@code null} if not requested. 161 */ 162 public AccessToken getAccessToken() { 163 164 return accessToken; 165 } 166 167 168 @Override 169 public Map<String,String> toParameters() 170 throws SerializeException { 171 172 Map<String,String> params = new HashMap<String,String>(); 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 @Override 193 public URL toURI() 194 throws SerializeException { 195 196 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 197 198 // Fragment or query string? 199 if (accessToken != null) 200 sb.append('#'); 201 else 202 sb.append('?'); 203 204 205 sb.append(URLUtils.serializeParameters(toParameters())); 206 207 208 try { 209 return new URL(sb.toString()); 210 211 } catch (MalformedURLException e) { 212 213 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 214 } 215 } 216 217 218 /** 219 * Parses an authorisation success response. 220 * 221 * @param redirectURI The base redirection URI. Must not be 222 * {@code null}. 223 * @param params The response parameters to parse. Must not be 224 * {@code null}. 225 * 226 * @return The authorisation success response. 227 * 228 * @throws ParseException If the parameters couldn't be parsed to an 229 * authorisation success response. 230 */ 231 public static AuthorizationSuccessResponse parse(final URL redirectURI, 232 final Map<String,String> params) 233 throws ParseException { 234 235 // Parse code parameter 236 237 AuthorizationCode code = null; 238 239 if (params.get("code") != null) 240 code = new AuthorizationCode(params.get("code")); 241 242 243 // Parse access_token parameters 244 245 AccessToken accessToken = null; 246 247 if (params.get("access_token") != null) { 248 249 JSONObject jsonObject = new JSONObject(); 250 251 jsonObject.putAll(params); 252 253 accessToken = AccessToken.parse(jsonObject); 254 } 255 256 257 // Parse optional state parameter 258 State state = State.parse(params.get("state")); 259 260 261 return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state); 262 } 263 264 265 /** 266 * Parses an authorisation success response. 267 * 268 * <p>Example URI: 269 * 270 * <pre> 271 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 272 * </pre> 273 * 274 * @param uri The URI to parse. Can be absolute or relative, with a 275 * fragment or query string containing the authorisation 276 * response parameters. Must not be {@code null}. 277 * 278 * @return The authorisation success response. 279 * 280 * @throws ParseException If the redirection URI couldn't be parsed to 281 * an authorisation success response. 282 */ 283 public static AuthorizationSuccessResponse parse(final URL uri) 284 throws ParseException { 285 286 String paramString = null; 287 288 if (uri.getQuery() != null) 289 paramString = uri.getQuery(); 290 291 else if (uri.getRef() != null) 292 paramString = uri.getRef(); 293 294 else 295 throw new ParseException("Missing authorization response parameters"); 296 297 Map<String,String> params = URLUtils.parseParameters(paramString); 298 299 if (params == null) 300 throw new ParseException("Missing or invalid authorization response parameters"); 301 302 return parse(URLUtils.getBaseURL(uri), params); 303 } 304 305 306 /** 307 * Parses an authorisation success response. 308 * 309 * <p>Example HTTP response: 310 * 311 * <pre> 312 * HTTP/1.1 302 Found 313 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 314 * </pre> 315 * 316 * @param httpResponse The HTTP response to parse. Must not be 317 * {@code null}. 318 * 319 * @return The authorisation success response. 320 * 321 * @throws ParseException If the HTTP response couldn't be parsed to an 322 * authorisation success response. 323 */ 324 public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse) 325 throws ParseException { 326 327 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 328 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 329 httpResponse.getStatusCode()); 330 331 URL location = httpResponse.getLocation(); 332 333 if (location == null) 334 throw new ParseException("Missing redirection URL / HTTP Location header"); 335 336 return parse(location); 337 } 338}