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.net.URISyntaxException; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026 027import com.nimbusds.jwt.JWT; 028import com.nimbusds.jwt.JWTParser; 029import com.nimbusds.oauth2.sdk.*; 030import com.nimbusds.oauth2.sdk.http.HTTPRequest; 031import com.nimbusds.oauth2.sdk.http.HTTPResponse; 032import com.nimbusds.oauth2.sdk.id.State; 033import com.nimbusds.oauth2.sdk.token.AccessToken; 034import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 035import com.nimbusds.oauth2.sdk.util.StringUtils; 036import com.nimbusds.oauth2.sdk.util.URIUtils; 037import com.nimbusds.oauth2.sdk.util.URLUtils; 038import net.jcip.annotations.Immutable; 039 040 041/** 042 * OpenID Connect authentication success response. Used to return an 043 * authorisation code, access token and / or ID Token at the Authorisation 044 * endpoint. 045 * 046 * <p>Example HTTP response with code and ID Token (code flow): 047 * 048 * <pre> 049 * HTTP/1.1 302 Found 050 * Location: https://client.example.org/cb# 051 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 052 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 053 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 054 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 055 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 056 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 057 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 058 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 059 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 060 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 061 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 062 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 063 * </pre> 064 * 065 * <p>Related specifications: 066 * 067 * <ul> 068 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 069 * 3.3.2.5 and 3.3.2.6. 070 * <li>OpenID Connect Session Management 1.0 - draft 23, section 3. 071 * <li>OAuth 2.0 (RFC 6749), section 3.1. 072 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 073 * <li>OAuth 2.0 Form Post Response Mode 1.0. 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 @Override 128 public ResponseType impliedResponseType() { 129 130 ResponseType rt = new ResponseType(); 131 132 if (getAuthorizationCode() != null) { 133 rt.add(ResponseType.Value.CODE); 134 } 135 136 if (getIDToken() != null) { 137 rt.add(OIDCResponseTypeValue.ID_TOKEN); 138 } 139 140 if (getAccessToken() != null) { 141 rt.add(ResponseType.Value.TOKEN); 142 } 143 144 return rt; 145 } 146 147 148 @Override 149 public ResponseMode impliedResponseMode() { 150 151 if (getResponseMode() != null) { 152 return getResponseMode(); 153 } else { 154 if (getAccessToken() != null || getIDToken() != null) { 155 return ResponseMode.FRAGMENT; 156 } else { 157 return ResponseMode.QUERY; 158 } 159 } 160 } 161 162 163 /** 164 * Gets the requested ID token. 165 * 166 * @return The ID token (ready for output), {@code null} if not 167 * requested. 168 */ 169 public JWT getIDToken() { 170 171 return idToken; 172 } 173 174 175 /** 176 * Gets the session state for session management. 177 * 178 * @return The session store, {@code null} if session management is not 179 * supported. 180 */ 181 public State getSessionState() { 182 183 return sessionState; 184 } 185 186 187 @Override 188 public Map<String,List<String>> toParameters() { 189 190 Map<String,List<String>> params = super.toParameters(); 191 192 if (idToken != null) { 193 194 try { 195 params.put("id_token", Collections.singletonList(idToken.serialize())); 196 197 } catch (IllegalStateException e) { 198 199 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 200 201 } 202 } 203 204 if (sessionState != null) { 205 206 params.put("session_state", Collections.singletonList(sessionState.getValue())); 207 } 208 209 return params; 210 } 211 212 213 @Override 214 public AuthenticationSuccessResponse toSuccessResponse() { 215 return this; 216 } 217 218 219 @Override 220 public AuthenticationErrorResponse toErrorResponse() { 221 throw new ClassCastException("Cannot cast to AuthenticationErrorResponse"); 222 } 223 224 225 /** 226 * Parses an OpenID Connect authentication success response. 227 * 228 * @param redirectURI The base redirection URI. Must not be 229 * {@code null}. 230 * @param params The response parameters to parse. Must not be 231 * {@code null}. 232 * 233 * @return The OpenID Connect authentication success response. 234 * 235 * @throws ParseException If the parameters couldn't be parsed to an 236 * OpenID Connect authentication success 237 * response. 238 */ 239 public static AuthenticationSuccessResponse parse(final URI redirectURI, 240 final Map<String,List<String>> params) 241 throws ParseException { 242 243 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 244 245 // Parse id_token parameter 246 String idTokenString = MultivaluedMapUtils.getFirstValue(params, "id_token"); 247 JWT idToken = null; 248 if (idTokenString != null) { 249 250 try { 251 idToken = JWTParser.parse(idTokenString); 252 253 } catch (java.text.ParseException e) { 254 255 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 256 } 257 } 258 259 // Parse the optional session_state parameter 260 261 State sessionState = null; 262 263 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "session_state"))) { 264 265 sessionState = new State(MultivaluedMapUtils.getFirstValue(params, "session_state")); 266 } 267 268 return new AuthenticationSuccessResponse(redirectURI, 269 asr.getAuthorizationCode(), 270 idToken, 271 asr.getAccessToken(), 272 asr.getState(), 273 sessionState, 274 null); 275 } 276 277 278 /** 279 * Parses an OpenID Connect authentication success response. 280 * 281 * <p>Use a relative URI if the host, port and path details are not 282 * known: 283 * 284 * <pre> 285 * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj"); 286 * </pre> 287 * 288 * <p>Example URI: 289 * 290 * <pre> 291 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 292 * </pre> 293 * 294 * @param uri The URI to parse. Can be absolute or relative, with a 295 * fragment or query string containing the authentication 296 * response parameters. Must not be {@code null}. 297 * 298 * @return The OpenID Connect authentication success response. 299 * 300 * @throws ParseException If the redirection URI couldn't be parsed to 301 * an OpenID Connect authentication success 302 * response. 303 */ 304 public static AuthenticationSuccessResponse parse(final URI uri) 305 throws ParseException { 306 307 Map<String,List<String>> params; 308 309 if (uri.getRawFragment() != null) { 310 311 params = URLUtils.parseParameters(uri.getRawFragment()); 312 313 } else if (uri.getRawQuery() != null) { 314 315 params = URLUtils.parseParameters(uri.getRawQuery()); 316 317 } else { 318 319 throw new ParseException("Missing URI fragment or query string"); 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 initial HTTP 302 redirect response generated at the 329 * authorisation endpoint. 330 * 331 * <p>Example HTTP response: 332 * 333 * <pre> 334 * HTTP/1.1 302 Found 335 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 336 * </pre> 337 * 338 * @see #parse(HTTPRequest) 339 * 340 * @param httpResponse The HTTP response to parse. Must not be 341 * {@code null}. 342 * 343 * @return The OpenID Connect authentication success response. 344 * 345 * @throws ParseException If the HTTP response couldn't be parsed to an 346 * OpenID Connect authentication success 347 * response. 348 */ 349 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 350 throws ParseException { 351 352 URI location = httpResponse.getLocation(); 353 354 if (location == null) 355 throw new ParseException("Missing redirection URI / HTTP Location header"); 356 357 return parse(location); 358 } 359 360 361 /** 362 * Parses an OpenID Connect authentication success response from the 363 * specified HTTP request at the client redirection (callback) URI. 364 * Applies to {@code query}, {@code fragment} and {@code form_post} 365 * response modes. 366 * 367 * <p>Example HTTP request (authentication success): 368 * 369 * <pre> 370 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 371 * Host: client.example.com 372 * </pre> 373 * 374 * @see #parse(HTTPResponse) 375 * 376 * @param httpRequest The HTTP request to parse. Must not be 377 * {@code null}. 378 * 379 * @return The authentication success response. 380 * 381 * @throws ParseException If the HTTP request couldn't be parsed to an 382 * OpenID Connect authentication success 383 * response. 384 */ 385 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 386 throws ParseException { 387 388 final URI baseURI; 389 390 try { 391 baseURI = httpRequest.getURL().toURI(); 392 393 } catch (URISyntaxException e) { 394 throw new ParseException(e.getMessage(), e); 395 } 396 397 if (httpRequest.getQuery() != null) { 398 // For query string and form_post response mode 399 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 400 } else if (httpRequest.getFragment() != null) { 401 // For fragment response mode (never available in actual HTTP request from browser) 402 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 403 } else { 404 throw new ParseException("Missing URI fragment, query string or post body"); 405 } 406 } 407}