001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.URI; 005import java.net.URISyntaxException; 006import java.net.URL; 007import java.util.HashMap; 008import java.util.Map; 009 010import net.jcip.annotations.Immutable; 011 012import net.minidev.json.JSONObject; 013 014import com.nimbusds.oauth2.sdk.id.State; 015import com.nimbusds.oauth2.sdk.token.AccessToken; 016import com.nimbusds.oauth2.sdk.http.HTTPResponse; 017import com.nimbusds.oauth2.sdk.util.URIUtils; 018import com.nimbusds.oauth2.sdk.util.URLUtils; 019 020 021/** 022 * Authorisation success response. Used to return an authorisation code or 023 * access token at the Authorisation endpoint. 024 * 025 * <p>Example HTTP response with code (code flow): 026 * 027 * <pre> 028 * HTTP/1.1 302 Found 029 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 030 * </pre> 031 * 032 * <p>Example HTTP response with access token (implicit flow): 033 * 034 * <pre> 035 * HTTP/1.1 302 Found 036 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 037 * &state=xyz&token_type=Bearer&expires_in=3600 038 * </pre> 039 * 040 * <p>Related specifications: 041 * 042 * <ul> 043 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2. 044 * </ul> 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 URI 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 URI 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 URI redirectURI, 115 final AuthorizationCode code, 116 final AccessToken accessToken, 117 final State state) { 118 119 super(redirectURI, state); 120 this.code = code; 121 this.accessToken = accessToken; 122 } 123 124 125 /** 126 * Returns the implied response type. 127 * 128 * @return The implied response type. 129 */ 130 public ResponseType impliedResponseType() { 131 132 ResponseType rt = new ResponseType(); 133 134 if (code != null) 135 rt.add(ResponseType.Value.CODE); 136 137 if (accessToken != null) 138 rt.add(ResponseType.Value.TOKEN); 139 140 return rt; 141 } 142 143 144 /** 145 * Gets the authorisation code. 146 * 147 * @return The authorisation code, {@code null} if not requested. 148 */ 149 public AuthorizationCode getAuthorizationCode() { 150 151 return code; 152 } 153 154 155 /** 156 * Gets the access token. 157 * 158 * @return The access token, {@code null} if not requested. 159 */ 160 public AccessToken getAccessToken() { 161 162 return accessToken; 163 } 164 165 166 @Override 167 public Map<String,String> toParameters() 168 throws SerializeException { 169 170 Map<String,String> params = new HashMap<String,String>(); 171 172 if (code != null) 173 params.put("code", code.getValue()); 174 175 if (accessToken != null) { 176 177 for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) { 178 179 params.put(entry.getKey(), entry.getValue().toString()); 180 } 181 } 182 183 if (getState() != null) 184 params.put("state", getState().getValue()); 185 186 return params; 187 } 188 189 190 @Override 191 public URI toURI() 192 throws SerializeException { 193 194 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 195 196 // Fragment or query string? 197 if (accessToken != null) { 198 sb.append('#'); 199 } else { 200 sb.append('?'); 201 } 202 203 sb.append(URLUtils.serializeParameters(toParameters())); 204 205 try { 206 return new URI(sb.toString()); 207 208 } catch (URISyntaxException 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 redirection URI. Must not be 219 * {@code null}. 220 * @param params The response parameters to parse. Must not be 221 * {@code null}. 222 * 223 * @return The authorisation success response. 224 * 225 * @throws ParseException If the parameters couldn't be parsed to an 226 * authorisation success response. 227 */ 228 public static AuthorizationSuccessResponse parse(final URI redirectURI, 229 final Map<String,String> params) 230 throws ParseException { 231 232 // Parse code parameter 233 234 AuthorizationCode code = null; 235 236 if (params.get("code") != null) 237 code = new AuthorizationCode(params.get("code")); 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 jsonObject.putAll(params); 247 accessToken = AccessToken.parse(jsonObject); 248 } 249 250 // Parse optional state parameter 251 State state = State.parse(params.get("state")); 252 253 return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state); 254 } 255 256 257 /** 258 * Parses an authorisation success response. 259 * 260 * <p>Example URI: 261 * 262 * <pre> 263 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 264 * </pre> 265 * 266 * @param uri The URI to parse. Can be absolute or relative, with a 267 * fragment or query string containing the authorisation 268 * response parameters. Must not be {@code null}. 269 * 270 * @return The authorisation success response. 271 * 272 * @throws ParseException If the redirection URI couldn't be parsed to 273 * an authorisation success response. 274 */ 275 public static AuthorizationSuccessResponse parse(final URI uri) 276 throws ParseException { 277 278 String paramString; 279 280 if (uri.getQuery() != null) { 281 282 paramString = uri.getRawQuery(); 283 284 } else if (uri.getRawFragment() != null) { 285 286 paramString = uri.getRawFragment(); 287 288 } else { 289 290 throw new ParseException("Missing authorization response parameters"); 291 } 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(URIUtils.getBaseURI(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 redirection URL / HTTP Location header"); 331 332 try { 333 return parse(location.toURI()); 334 335 } catch (URISyntaxException e) { 336 337 throw new ParseException(e.getMessage(), e); 338 } 339 } 340}