001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.URI; 005import java.net.URISyntaxException; 006import java.util.Map; 007 008import net.jcip.annotations.Immutable; 009 010import com.nimbusds.jwt.JWT; 011import com.nimbusds.jwt.JWTParser; 012 013import com.nimbusds.oauth2.sdk.AuthorizationCode; 014import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse; 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.ResponseType; 017import com.nimbusds.oauth2.sdk.SerializeException; 018import com.nimbusds.oauth2.sdk.id.State; 019import com.nimbusds.oauth2.sdk.http.HTTPResponse; 020import com.nimbusds.oauth2.sdk.token.AccessToken; 021import com.nimbusds.oauth2.sdk.util.URIUtils; 022import com.nimbusds.oauth2.sdk.util.URLUtils; 023import org.apache.commons.lang3.StringUtils; 024 025 026/** 027 * OpenID Connect authentication success response. Used to return an 028 * authorisation code, access token and / or ID Token at the Authorisation 029 * endpoint. 030 * 031 * <p>Example HTTP response with code and ID Token (code flow): 032 * 033 * <pre> 034 * HTTP/1.1 302 Found 035 * Location: https://client.example.org/cb# 036 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 037 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 038 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 039 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 040 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 041 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 042 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 043 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 044 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 045 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 046 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 047 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 048 * </pre> 049 * 050 * <p>Related specifications: 051 * 052 * <ul> 053 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 054 * 3.3.2.5 and 3.3.2.6. 055 * <li>OpenID Connect Session Management 1.0 - draft 22, section 3. 056 * </ul> 057 */ 058@Immutable 059public class AuthenticationSuccessResponse 060 extends AuthorizationSuccessResponse 061 implements AuthenticationResponse { 062 063 064 /** 065 * The ID token, if requested. 066 */ 067 private final JWT idToken; 068 069 070 /** 071 * The session state, required if session management is supported. 072 */ 073 private final State sessionState; 074 075 076 /** 077 * Creates a new OpenID Connect authentication success response. 078 * 079 * @param redirectURI The requested redirection URI. Must not be 080 * {@code null}. 081 * @param code The authorisation code, {@code null} if not 082 * requested. 083 * @param idToken The ID token (ready for output), {@code null} if 084 * not requested. 085 * @param accessToken The UserInfo access token, {@code null} if not 086 * requested. 087 * @param state The state, {@code null} if not requested. 088 */ 089 public AuthenticationSuccessResponse(final URI redirectURI, 090 final AuthorizationCode code, 091 final JWT idToken, 092 final AccessToken accessToken, 093 final State state) { 094 095 this(redirectURI, code, idToken, accessToken, state, null); 096 } 097 098 099 /** 100 * Creates a new OpenID Connect authentication success response. 101 * 102 * @param redirectURI The requested redirection URI. Must not be 103 * {@code null}. 104 * @param code The authorisation code, {@code null} if not 105 * requested. 106 * @param idToken The ID token (ready for output), {@code null} if 107 * not requested. 108 * @param accessToken The UserInfo access token, {@code null} if not 109 * requested. 110 * @param state The state, {@code null} if not requested. 111 * @param sessionState The session store, {@code null} if session 112 * management is not supported. 113 */ 114 public AuthenticationSuccessResponse(final URI redirectURI, 115 final AuthorizationCode code, 116 final JWT idToken, 117 final AccessToken accessToken, 118 final State state, 119 final State sessionState) { 120 121 super(redirectURI, code, accessToken, state); 122 123 this.idToken = idToken; 124 125 this.sessionState = sessionState; 126 } 127 128 129 @Override 130 public ResponseType impliedResponseType() { 131 132 ResponseType rt = new ResponseType(); 133 134 if (getAuthorizationCode() != null) { 135 rt.add(ResponseType.Value.CODE); 136 } 137 138 if (getIDToken() != null) { 139 rt.add(OIDCResponseTypeValue.ID_TOKEN); 140 } 141 142 if (getAccessToken() != null) { 143 rt.add(ResponseType.Value.TOKEN); 144 } 145 146 return rt; 147 } 148 149 150 /** 151 * Gets the requested ID token. 152 * 153 * @return The ID token (ready for output), {@code null} if not 154 * requested. 155 */ 156 public JWT getIDToken() { 157 158 return idToken; 159 } 160 161 162 /** 163 * Gets the session state for session management. 164 * 165 * @return The session store, {@code null} if session management is not 166 * supported. 167 */ 168 public State getSessionState() { 169 170 return sessionState; 171 } 172 173 174 @Override 175 public Map<String,String> toParameters() 176 throws SerializeException { 177 178 Map<String,String> params = super.toParameters(); 179 180 if (idToken != null) { 181 182 try { 183 params.put("id_token", idToken.serialize()); 184 185 } catch (IllegalStateException e) { 186 187 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 188 189 } 190 } 191 192 if (sessionState != null) { 193 194 params.put("session_state", sessionState.getValue()); 195 } 196 197 return params; 198 } 199 200 201 @Override 202 public URI toURI() 203 throws SerializeException { 204 205 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 206 207 // Fragment or query string? 208 if (idToken != null || getAccessToken() != null) { 209 sb.append('#'); 210 } else { 211 sb.append('?'); 212 } 213 214 sb.append(URLUtils.serializeParameters(toParameters())); 215 216 try { 217 return new URI(sb.toString()); 218 219 } catch (URISyntaxException e) { 220 221 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 222 } 223 } 224 225 226 /** 227 * Parses an OpenID Connect authentication success response from the 228 * specified redirection URI and parameters. 229 * 230 * @param redirectURI The base redirection URI. Must not be 231 * {@code null}. 232 * @param params The response parameters to parse. Must not be 233 * {@code null}. 234 * 235 * @return The OpenID Connect authentication success response. 236 * 237 * @throws ParseException If the parameters couldn't be parsed to an 238 * OpenID Connect authentication success 239 * response. 240 */ 241 public static AuthenticationSuccessResponse parse(final URI redirectURI, 242 final Map<String,String> params) 243 throws ParseException { 244 245 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 246 247 // Parse id_token parameter 248 JWT idToken = null; 249 250 if (params.get("id_token") != null) { 251 252 try { 253 idToken = JWTParser.parse(params.get("id_token")); 254 255 } catch (java.text.ParseException e) { 256 257 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 258 } 259 } 260 261 // Parse the optional session_state parameter 262 263 State sessionState = null; 264 265 if (StringUtils.isNotBlank(params.get("session_state"))) { 266 267 sessionState = new State(params.get("session_state")); 268 } 269 270 return new AuthenticationSuccessResponse(redirectURI, 271 asr.getAuthorizationCode(), 272 idToken, 273 asr.getAccessToken(), 274 asr.getState(), 275 sessionState); 276 } 277 278 279 /** 280 * Parses an OpenID Connect authentication success response from the 281 * specified URI. 282 * 283 * <p>Example URI: 284 * 285 * <pre> 286 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 287 * </pre> 288 * 289 * @param uri The URI to parse. Can be absolute or relative, with a 290 * fragment or query string containing the authentication 291 * response parameters. Must not be {@code null}. 292 * 293 * @return The OpenID Connect authentication success response. 294 * 295 * @throws ParseException If the redirection URI couldn't be parsed to 296 * an OpenID Connect authentication success 297 * response. 298 */ 299 public static AuthenticationSuccessResponse parse(final URI uri) 300 throws ParseException { 301 302 String paramString; 303 304 if (uri.getRawQuery() != null) { 305 306 paramString = uri.getRawQuery(); 307 308 } else if (uri.getRawFragment() != null) { 309 310 paramString = uri.getRawFragment(); 311 312 } else { 313 throw new ParseException("Missing authorization response parameters"); 314 } 315 316 Map<String,String> params = URLUtils.parseParameters(paramString); 317 318 if (params == null) { 319 throw new ParseException("Missing or invalid authorization response parameters"); 320 } 321 322 return parse(URIUtils.getBaseURI(uri), params); 323 } 324 325 326 /** 327 * Parses an OpenID Connect authentication success response from the 328 * specified HTTP response. 329 * 330 * <p>Example HTTP response: 331 * 332 * <pre> 333 * HTTP/1.1 302 Found 334 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 335 * </pre> 336 * 337 * @param httpResponse The HTTP response to parse. Must not be 338 * {@code null}. 339 * 340 * @return The OpenID Connect authentication success response. 341 * 342 * @throws ParseException If the HTTP response couldn't be parsed to an 343 * OpenID Connect authentication success 344 * response. 345 */ 346 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 347 throws ParseException { 348 349 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 350 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 351 httpResponse.getStatusCode()); 352 353 URI location = httpResponse.getLocation(); 354 355 if (location == null) 356 throw new ParseException("Missing redirection URI / HTTP Location header"); 357 358 return parse(location); 359 } 360}