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 net.jcip.annotations.Immutable; 026 027import org.apache.commons.lang3.StringUtils; 028 029import com.nimbusds.jwt.JWT; 030import com.nimbusds.jwt.JWTParser; 031 032import com.nimbusds.oauth2.sdk.*; 033import com.nimbusds.oauth2.sdk.id.State; 034import com.nimbusds.oauth2.sdk.http.HTTPRequest; 035import com.nimbusds.oauth2.sdk.http.HTTPResponse; 036import com.nimbusds.oauth2.sdk.token.AccessToken; 037import com.nimbusds.oauth2.sdk.util.URIUtils; 038import com.nimbusds.oauth2.sdk.util.URLUtils; 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,String> toParameters() { 189 190 Map<String,String> params = super.toParameters(); 191 192 if (idToken != null) { 193 194 try { 195 params.put("id_token", 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", sessionState.getValue()); 207 } 208 209 return params; 210 } 211 212 213 /** 214 * Parses an OpenID Connect authentication success response. 215 * 216 * @param redirectURI The base redirection URI. Must not be 217 * {@code null}. 218 * @param params The response parameters to parse. Must not be 219 * {@code null}. 220 * 221 * @return The OpenID Connect authentication success response. 222 * 223 * @throws ParseException If the parameters couldn't be parsed to an 224 * OpenID Connect authentication success 225 * response. 226 */ 227 public static AuthenticationSuccessResponse parse(final URI redirectURI, 228 final Map<String,String> params) 229 throws ParseException { 230 231 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 232 233 // Parse id_token parameter 234 JWT idToken = null; 235 236 if (params.get("id_token") != null) { 237 238 try { 239 idToken = JWTParser.parse(params.get("id_token")); 240 241 } catch (java.text.ParseException e) { 242 243 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 244 } 245 } 246 247 // Parse the optional session_state parameter 248 249 State sessionState = null; 250 251 if (StringUtils.isNotBlank(params.get("session_state"))) { 252 253 sessionState = new State(params.get("session_state")); 254 } 255 256 return new AuthenticationSuccessResponse(redirectURI, 257 asr.getAuthorizationCode(), 258 idToken, 259 asr.getAccessToken(), 260 asr.getState(), 261 sessionState, 262 null); 263 } 264 265 266 /** 267 * Parses an OpenID Connect authentication success response. 268 * 269 * <p>Use a relative URI if the host, port and path details are not 270 * known: 271 * 272 * <pre> 273 * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj"); 274 * </pre> 275 * 276 * <p>Example URI: 277 * 278 * <pre> 279 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 280 * </pre> 281 * 282 * @param uri The URI to parse. Can be absolute or relative, with a 283 * fragment or query string containing the authentication 284 * response parameters. Must not be {@code null}. 285 * 286 * @return The OpenID Connect authentication success response. 287 * 288 * @throws ParseException If the redirection URI couldn't be parsed to 289 * an OpenID Connect authentication success 290 * response. 291 */ 292 public static AuthenticationSuccessResponse parse(final URI uri) 293 throws ParseException { 294 295 Map<String,String> params; 296 297 if (uri.getRawFragment() != null) { 298 299 params = URLUtils.parseParameters(uri.getRawFragment()); 300 301 } else if (uri.getRawQuery() != null) { 302 303 params = URLUtils.parseParameters(uri.getRawQuery()); 304 305 } else { 306 307 throw new ParseException("Missing URI fragment or query string"); 308 } 309 310 return parse(URIUtils.getBaseURI(uri), params); 311 } 312 313 314 /** 315 * Parses an OpenID Connect authentication success response from the 316 * specified initial HTTP 302 redirect response generated at the 317 * authorisation endpoint. 318 * 319 * <p>Example HTTP response: 320 * 321 * <pre> 322 * HTTP/1.1 302 Found 323 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 324 * </pre> 325 * 326 * @see #parse(HTTPRequest) 327 * 328 * @param httpResponse The HTTP response to parse. Must not be 329 * {@code null}. 330 * 331 * @return The OpenID Connect authentication success response. 332 * 333 * @throws ParseException If the HTTP response couldn't be parsed to an 334 * OpenID Connect authentication success 335 * response. 336 */ 337 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 338 throws ParseException { 339 340 URI location = httpResponse.getLocation(); 341 342 if (location == null) 343 throw new ParseException("Missing redirection URI / HTTP Location header"); 344 345 return parse(location); 346 } 347 348 349 /** 350 * Parses an OpenID Connect authentication success response from the 351 * specified HTTP request at the client redirection (callback) URI. 352 * Applies to {@code query}, {@code fragment} and {@code form_post} 353 * response modes. 354 * 355 * <p>Example HTTP request (authentication success): 356 * 357 * <pre> 358 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 359 * Host: client.example.com 360 * </pre> 361 * 362 * @see #parse(HTTPResponse) 363 * 364 * @param httpRequest The HTTP request to parse. Must not be 365 * {@code null}. 366 * 367 * @throws ParseException If the HTTP request couldn't be parsed to an 368 * OpenID Connect authentication success 369 * response. 370 */ 371 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 372 throws ParseException { 373 374 final URI baseURI; 375 376 try { 377 baseURI = httpRequest.getURL().toURI(); 378 379 } catch (URISyntaxException e) { 380 throw new ParseException(e.getMessage(), e); 381 } 382 383 if (httpRequest.getQuery() != null) { 384 // For query string and form_post response mode 385 return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery())); 386 } else if (httpRequest.getFragment() != null) { 387 // For fragment response mode (never available in actual HTTP request from browser) 388 return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment())); 389 } else { 390 throw new ParseException("Missing URI fragment, query string or post body"); 391 } 392 } 393}