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