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