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. 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@Immutable 045public class AuthorizationSuccessResponse 046 extends AuthorizationResponse 047 implements SuccessResponse { 048 049 050 /** 051 * The authorisation code, if requested. 052 */ 053 private final AuthorizationCode code; 054 055 056 /** 057 * The access token, if requested. 058 */ 059 private final AccessToken accessToken; 060 061 062 /** 063 * Creates a new authorisation success response in the code flow 064 * (authorisation code grant). 065 * 066 * @param redirectURI The base redirection URI. Must not be 067 * {@code null}. 068 * @param code The authorisation code. Must not be {@code null}. 069 * @param state The state, {@code null} if not requested. 070 */ 071 public AuthorizationSuccessResponse(final URL redirectURI, 072 final AuthorizationCode code, 073 final State state) { 074 075 this(redirectURI, code, null, state); 076 077 if (code == null) 078 throw new IllegalArgumentException("The authorization code must not be null"); 079 } 080 081 082 /** 083 * Creates a new authorisation success response in the implicit flow 084 * (implicit grant). 085 * 086 * @param redirectURI The base redirection URI. Must not be 087 * {@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 redirection URI. Must not be 106 * {@code null}. 107 * @param code The authorisation code, {@code null} if not 108 * requested. 109 * @param accessToken The access token, {@code null} if not requested. 110 * @param state The state, {@code null} if not requested. 111 */ 112 public AuthorizationSuccessResponse(final URL redirectURI, 113 final AuthorizationCode code, 114 final AccessToken accessToken, 115 final State state) { 116 117 super(redirectURI, state); 118 this.code = code; 119 this.accessToken = accessToken; 120 } 121 122 123 /** 124 * Returns the implied response type. 125 * 126 * @return The implied response type. 127 */ 128 public ResponseType impliedResponseType() { 129 130 ResponseType rt = new ResponseType(); 131 132 if (code != null) 133 rt.add(ResponseType.Value.CODE); 134 135 if (accessToken != null) 136 rt.add(ResponseType.Value.TOKEN); 137 138 return rt; 139 } 140 141 142 /** 143 * Gets the authorisation code. 144 * 145 * @return The authorisation code, {@code null} if not requested. 146 */ 147 public AuthorizationCode getAuthorizationCode() { 148 149 return code; 150 } 151 152 153 /** 154 * Gets the access token. 155 * 156 * @return The access token, {@code null} if not requested. 157 */ 158 public AccessToken getAccessToken() { 159 160 return accessToken; 161 } 162 163 164 @Override 165 public Map<String,String> toParameters() 166 throws SerializeException { 167 168 Map<String,String> params = new HashMap<String,String>(); 169 170 if (code != null) 171 params.put("code", code.getValue()); 172 173 if (accessToken != null) { 174 175 for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) { 176 177 params.put(entry.getKey(), entry.getValue().toString()); 178 } 179 } 180 181 if (getState() != null) 182 params.put("state", getState().getValue()); 183 184 return params; 185 } 186 187 188 @Override 189 public URL toURI() 190 throws SerializeException { 191 192 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 193 194 // Fragment or query string? 195 if (accessToken != null) { 196 sb.append('#'); 197 } else { 198 sb.append('?'); 199 } 200 201 sb.append(URLUtils.serializeParameters(toParameters())); 202 203 try { 204 return new URL(sb.toString()); 205 206 } catch (MalformedURLException e) { 207 208 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 209 } 210 } 211 212 213 /** 214 * Parses an authorisation success response. 215 * 216 * @param redirectURI The base redirection URI. Must not be 217 * {@code null}. 218 * @param params The response parameters to parse. Must not be 219 * {@code null}. 220 * 221 * @return The authorisation success response. 222 * 223 * @throws ParseException If the parameters couldn't be parsed to an 224 * authorisation success response. 225 */ 226 public static AuthorizationSuccessResponse parse(final URL redirectURI, 227 final Map<String,String> params) 228 throws ParseException { 229 230 // Parse code parameter 231 232 AuthorizationCode code = null; 233 234 if (params.get("code") != null) 235 code = new AuthorizationCode(params.get("code")); 236 237 // Parse access_token parameters 238 239 AccessToken accessToken = null; 240 241 if (params.get("access_token") != null) { 242 243 JSONObject jsonObject = new JSONObject(); 244 jsonObject.putAll(params); 245 accessToken = AccessToken.parse(jsonObject); 246 } 247 248 // Parse optional state parameter 249 State state = State.parse(params.get("state")); 250 251 return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state); 252 } 253 254 255 /** 256 * Parses an authorisation success response. 257 * 258 * <p>Example URI: 259 * 260 * <pre> 261 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 262 * </pre> 263 * 264 * @param uri The URI to parse. Can be absolute or relative, with a 265 * fragment or query string containing the authorisation 266 * response parameters. Must not be {@code null}. 267 * 268 * @return The authorisation success response. 269 * 270 * @throws ParseException If the redirection URI couldn't be parsed to 271 * an authorisation success response. 272 */ 273 public static AuthorizationSuccessResponse parse(final URL uri) 274 throws ParseException { 275 276 String paramString; 277 278 if (uri.getQuery() != null) { 279 280 paramString = uri.getQuery(); 281 282 } else if (uri.getRef() != null) { 283 284 paramString = uri.getRef(); 285 286 } else { 287 288 throw new ParseException("Missing authorization response parameters"); 289 } 290 291 Map<String,String> params = URLUtils.parseParameters(paramString); 292 293 if (params == null) 294 throw new ParseException("Missing or invalid authorization response parameters"); 295 296 return parse(URLUtils.getBaseURL(uri), params); 297 } 298 299 300 /** 301 * Parses an authorisation success response. 302 * 303 * <p>Example HTTP response: 304 * 305 * <pre> 306 * HTTP/1.1 302 Found 307 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 308 * </pre> 309 * 310 * @param httpResponse The HTTP response to parse. Must not be 311 * {@code null}. 312 * 313 * @return The authorisation success response. 314 * 315 * @throws ParseException If the HTTP response couldn't be parsed to an 316 * authorisation success response. 317 */ 318 public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse) 319 throws ParseException { 320 321 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 322 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 323 httpResponse.getStatusCode()); 324 325 URL location = httpResponse.getLocation(); 326 327 if (location == null) 328 throw new ParseException("Missing redirection URL / HTTP Location header"); 329 330 return parse(location); 331 } 332}