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