001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk; 019 020 021import java.net.URI; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025 026import com.nimbusds.jwt.JWT; 027import com.nimbusds.jwt.JWTParser; 028import com.nimbusds.oauth2.sdk.*; 029import com.nimbusds.oauth2.sdk.http.HTTPRequest; 030import com.nimbusds.oauth2.sdk.http.HTTPResponse; 031import com.nimbusds.oauth2.sdk.id.State; 032import com.nimbusds.oauth2.sdk.token.AccessToken; 033import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 034import com.nimbusds.oauth2.sdk.util.StringUtils; 035import com.nimbusds.oauth2.sdk.util.URIUtils; 036import net.jcip.annotations.Immutable; 037 038 039/** 040 * OpenID Connect authentication success response. Used to return an 041 * authorisation code, access token and / or ID Token at the Authorisation 042 * endpoint. 043 * 044 * <p>Example HTTP response with code and ID Token (code flow): 045 * 046 * <pre> 047 * HTTP/1.1 302 Found 048 * Location: https://client.example.org/cb# 049 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 050 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 051 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 052 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 053 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 054 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 055 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 056 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 057 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 058 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 059 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 060 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 061 * </pre> 062 * 063 * <p>Related specifications: 064 * 065 * <ul> 066 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 067 * 3.3.2.5 and 3.3.2.6. 068 * <li>OpenID Connect Session Management 1.0 - draft 23, section 3. 069 * <li>OAuth 2.0 (RFC 6749), section 3.1. 070 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 071 * <li>OAuth 2.0 Form Post Response Mode 1.0. 072 * <li>Financial-grade API: JWT Secured Authorization Response Mode for 073 * OAuth 2.0 (JARM). 074 * </ul> 075 */ 076@Immutable 077public class AuthenticationSuccessResponse 078 extends AuthorizationSuccessResponse 079 implements AuthenticationResponse { 080 081 082 /** 083 * The ID token, if requested. 084 */ 085 private final JWT idToken; 086 087 088 /** 089 * The session state, required if session management is supported. 090 */ 091 private final State sessionState; 092 093 094 /** 095 * Creates a new OpenID Connect authentication success response. 096 * 097 * @param redirectURI The requested redirection URI. Must not be 098 * {@code null}. 099 * @param code The authorisation code, {@code null} if not 100 * requested. 101 * @param idToken The ID token (ready for output), {@code null} if 102 * not requested. 103 * @param accessToken The UserInfo access token, {@code null} if not 104 * requested. 105 * @param state The state, {@code null} if not requested. 106 * @param sessionState The session store, {@code null} if session 107 * management is not supported. 108 * @param rm The response mode, {@code null} if not 109 * specified. 110 */ 111 public AuthenticationSuccessResponse(final URI redirectURI, 112 final AuthorizationCode code, 113 final JWT idToken, 114 final AccessToken accessToken, 115 final State state, 116 final State sessionState, 117 final ResponseMode rm) { 118 119 super(redirectURI, code, accessToken, state, rm); 120 121 this.idToken = idToken; 122 123 this.sessionState = sessionState; 124 } 125 126 127 /** 128 * Creates a new JSON Web Token (JWT) secured OpenID Connect 129 * authentication success response. 130 * 131 * @param redirectURI The requested redirection URI. Must not be 132 * {@code null}. 133 * @param jwtResponse The JWT-secured response. Must not be 134 * {@code null}. 135 * @param rm The response mode, {@code null} if not specified. 136 */ 137 public AuthenticationSuccessResponse(final URI redirectURI, 138 final JWT jwtResponse, 139 final ResponseMode rm) { 140 141 super(redirectURI, jwtResponse, rm); 142 idToken = null; 143 sessionState = null; 144 } 145 146 147 @Override 148 public ResponseType impliedResponseType() { 149 150 ResponseType rt = new ResponseType(); 151 152 if (getAuthorizationCode() != null) { 153 rt.add(ResponseType.Value.CODE); 154 } 155 156 if (getIDToken() != null) { 157 rt.add(OIDCResponseTypeValue.ID_TOKEN); 158 } 159 160 if (getAccessToken() != null) { 161 rt.add(ResponseType.Value.TOKEN); 162 } 163 164 return rt; 165 } 166 167 168 @Override 169 public ResponseMode impliedResponseMode() { 170 171 if (getResponseMode() != null) { 172 return getResponseMode(); 173 } else { 174 if (getJWTResponse() != null) { 175 // JARM 176 return ResponseMode.JWT; 177 } else if (getAccessToken() != null || getIDToken() != null) { 178 return ResponseMode.FRAGMENT; 179 } else { 180 return ResponseMode.QUERY; 181 } 182 } 183 } 184 185 186 /** 187 * Gets the requested ID token. 188 * 189 * @return The ID token (ready for output), {@code null} if not 190 * requested. 191 */ 192 public JWT getIDToken() { 193 194 return idToken; 195 } 196 197 198 /** 199 * Gets the session state for session management. 200 * 201 * @return The session store, {@code null} if session management is not 202 * supported. 203 */ 204 public State getSessionState() { 205 206 return sessionState; 207 } 208 209 210 @Override 211 public Map<String,List<String>> toParameters() { 212 213 Map<String,List<String>> params = super.toParameters(); 214 215 if (getJWTResponse() != null) { 216 // JARM, no other top-level parameters 217 return params; 218 } 219 220 if (idToken != null) { 221 222 try { 223 params.put("id_token", Collections.singletonList(idToken.serialize())); 224 225 } catch (IllegalStateException e) { 226 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 227 } 228 } 229 230 if (sessionState != null) { 231 232 params.put("session_state", Collections.singletonList(sessionState.getValue())); 233 } 234 235 return params; 236 } 237 238 239 @Override 240 public AuthenticationSuccessResponse toSuccessResponse() { 241 return this; 242 } 243 244 245 @Override 246 public AuthenticationErrorResponse toErrorResponse() { 247 throw new ClassCastException("Cannot cast to AuthenticationErrorResponse"); 248 } 249 250 251 /** 252 * Parses an OpenID Connect authentication success response. 253 * 254 * @param redirectURI The base redirection URI. Must not be 255 * {@code null}. 256 * @param params The response parameters to parse. Must not be 257 * {@code null}. 258 * 259 * @return The OpenID Connect authentication success response. 260 * 261 * @throws ParseException If the parameters couldn't be parsed to an 262 * OpenID Connect authentication success 263 * response. 264 */ 265 public static AuthenticationSuccessResponse parse(final URI redirectURI, 266 final Map<String,List<String>> params) 267 throws ParseException { 268 269 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 270 271 // JARM, ignore other top level params 272 if (asr.getJWTResponse() != null) { 273 return new AuthenticationSuccessResponse(redirectURI, asr.getJWTResponse(), asr.getResponseMode()); 274 } 275 276 // Parse id_token parameter 277 String idTokenString = MultivaluedMapUtils.getFirstValue(params, "id_token"); 278 JWT idToken = null; 279 if (idTokenString != null) { 280 281 try { 282 idToken = JWTParser.parse(idTokenString); 283 284 } catch (java.text.ParseException e) { 285 286 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 287 } 288 } 289 290 // Parse the optional session_state parameter 291 292 State sessionState = null; 293 294 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "session_state"))) { 295 296 sessionState = new State(MultivaluedMapUtils.getFirstValue(params, "session_state")); 297 } 298 299 return new AuthenticationSuccessResponse(redirectURI, 300 asr.getAuthorizationCode(), 301 idToken, 302 asr.getAccessToken(), 303 asr.getState(), 304 sessionState, 305 null); 306 } 307 308 309 /** 310 * Parses an OpenID Connect authentication success response. 311 * 312 * <p>Use a relative URI if the host, port and path details are not 313 * known: 314 * 315 * <pre> 316 * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj"); 317 * </pre> 318 * 319 * <p>Example URI: 320 * 321 * <pre> 322 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 323 * </pre> 324 * 325 * @param uri The URI to parse. Can be absolute or relative, with a 326 * fragment or query string containing the authentication 327 * response parameters. Must not be {@code null}. 328 * 329 * @return The OpenID Connect authentication success response. 330 * 331 * @throws ParseException If the redirection URI couldn't be parsed to 332 * an OpenID Connect authentication success 333 * response. 334 */ 335 public static AuthenticationSuccessResponse parse(final URI uri) 336 throws ParseException { 337 338 return parse(URIUtils.getBaseURI(uri), parseResponseParameters(uri)); 339 } 340 341 342 /** 343 * Parses an OpenID Connect authentication success response from the 344 * specified initial HTTP 302 redirect response generated at the 345 * authorisation endpoint. 346 * 347 * <p>Example HTTP response: 348 * 349 * <pre> 350 * HTTP/1.1 302 Found 351 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 352 * </pre> 353 * 354 * @see #parse(HTTPRequest) 355 * 356 * @param httpResponse The HTTP response to parse. Must not be 357 * {@code null}. 358 * 359 * @return The OpenID Connect authentication success response. 360 * 361 * @throws ParseException If the HTTP response couldn't be parsed to an 362 * OpenID Connect authentication success 363 * response. 364 */ 365 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 366 throws ParseException { 367 368 URI location = httpResponse.getLocation(); 369 370 if (location == null) 371 throw new ParseException("Missing redirection URI / HTTP Location header"); 372 373 return parse(location); 374 } 375 376 377 /** 378 * Parses an OpenID Connect authentication success response from the 379 * specified HTTP request at the client redirection (callback) URI. 380 * Applies to {@code query}, {@code fragment} and {@code form_post} 381 * response modes. 382 * 383 * <p>Example HTTP request (authentication success): 384 * 385 * <pre> 386 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 387 * Host: client.example.com 388 * </pre> 389 * 390 * @see #parse(HTTPResponse) 391 * 392 * @param httpRequest The HTTP request to parse. Must not be 393 * {@code null}. 394 * 395 * @return The authentication success response. 396 * 397 * @throws ParseException If the HTTP request couldn't be parsed to an 398 * OpenID Connect authentication success 399 * response. 400 */ 401 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 402 throws ParseException { 403 404 return parse(httpRequest.getURI(), parseResponseParameters(httpRequest)); 405 } 406}