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 173 Map<String,String> params = super.toParameters(); 174 175 if (idToken != null) { 176 177 try { 178 params.put("id_token", idToken.serialize()); 179 180 } catch (IllegalStateException e) { 181 182 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 183 184 } 185 } 186 187 if (sessionState != null) { 188 189 params.put("session_state", sessionState.getValue()); 190 } 191 192 return params; 193 } 194 195 196 /** 197 * Parses an OpenID Connect authentication success response. 198 * 199 * @param redirectURI The base redirection URI. Must not be 200 * {@code null}. 201 * @param params The response parameters to parse. Must not be 202 * {@code null}. 203 * 204 * @return The OpenID Connect authentication success response. 205 * 206 * @throws ParseException If the parameters couldn't be parsed to an 207 * OpenID Connect authentication success 208 * response. 209 */ 210 public static AuthenticationSuccessResponse parse(final URI redirectURI, 211 final Map<String,String> params) 212 throws ParseException { 213 214 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 215 216 // Parse id_token parameter 217 JWT idToken = null; 218 219 if (params.get("id_token") != null) { 220 221 try { 222 idToken = JWTParser.parse(params.get("id_token")); 223 224 } catch (java.text.ParseException e) { 225 226 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 227 } 228 } 229 230 // Parse the optional session_state parameter 231 232 State sessionState = null; 233 234 if (StringUtils.isNotBlank(params.get("session_state"))) { 235 236 sessionState = new State(params.get("session_state")); 237 } 238 239 return new AuthenticationSuccessResponse(redirectURI, 240 asr.getAuthorizationCode(), 241 idToken, 242 asr.getAccessToken(), 243 asr.getState(), 244 sessionState, 245 null); 246 } 247 248 249 /** 250 * Parses an OpenID Connect authentication success response. 251 * 252 * <p>Use a relative URI if the host, port and path details are not 253 * known: 254 * 255 * <pre> 256 * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj"); 257 * </pre> 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 authentication 267 * response parameters. Must not be {@code null}. 268 * 269 * @return The OpenID Connect authentication success response. 270 * 271 * @throws ParseException If the redirection URI couldn't be parsed to 272 * an OpenID Connect authentication success 273 * response. 274 */ 275 public static AuthenticationSuccessResponse parse(final URI uri) 276 throws ParseException { 277 278 Map<String,String> params; 279 280 if (uri.getRawFragment() != null) { 281 282 params = URLUtils.parseParameters(uri.getRawFragment()); 283 284 } else if (uri.getRawQuery() != null) { 285 286 params = URLUtils.parseParameters(uri.getRawQuery()); 287 288 } else { 289 290 throw new ParseException("Missing URI fragment or query string"); 291 } 292 293 return parse(URIUtils.getBaseURI(uri), params); 294 } 295 296 297 /** 298 * Parses an OpenID Connect authentication success response from the 299 * specified initial HTTP 302 redirect response generated at the 300 * authorisation endpoint. 301 * 302 * <p>Example HTTP response: 303 * 304 * <pre> 305 * HTTP/1.1 302 Found 306 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 307 * </pre> 308 * 309 * @see #parse(HTTPRequest) 310 * 311 * @param httpResponse The HTTP response to parse. Must not be 312 * {@code null}. 313 * 314 * @return The OpenID Connect authentication success response. 315 * 316 * @throws ParseException If the HTTP response couldn't be parsed to an 317 * OpenID Connect authentication success 318 * response. 319 */ 320 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 321 throws ParseException { 322 323 URI location = httpResponse.getLocation(); 324 325 if (location == null) 326 throw new ParseException("Missing redirection URI / HTTP Location header"); 327 328 return parse(location); 329 } 330 331 332 /** 333 * Parses an OpenID Connect authentication success response from the 334 * specified HTTP request at the client redirection (callback) URI. 335 * Applies to {@code query}, {@code fragment} and {@code form_post} 336 * response modes. 337 * 338 * <p>Example HTTP request (authentication success): 339 * 340 * <pre> 341 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 342 * Host: client.example.com 343 * </pre> 344 * 345 * @see #parse(HTTPResponse) 346 * 347 * @param httpRequest The HTTP request to parse. Must not be 348 * {@code null}. 349 * 350 * @throws ParseException If the HTTP request couldn't be parsed to an 351 * OpenID Connect authentication success 352 * response. 353 */ 354 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 355 throws ParseException { 356 357 final URI baseURI; 358 359 try { 360 baseURI = httpRequest.getURL().toURI(); 361 362 } catch (URISyntaxException e) { 363 throw new ParseException(e.getMessage(), e); 364 } 365 366 if (httpRequest.getQuery() != null) { 367 // For query string and form_post response mode 368 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 369 } else if (httpRequest.getFragment() != null) { 370 // For fragment response mode (never available in actual HTTP request from browser) 371 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 372 } else { 373 throw new ParseException("Missing URI fragment, query string or post body"); 374 } 375 } 376}