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