001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.URL; 005import java.util.Map; 006 007import net.jcip.annotations.Immutable; 008 009import com.nimbusds.jwt.JWT; 010import com.nimbusds.jwt.JWTParser; 011 012import com.nimbusds.oauth2.sdk.AuthorizationCode; 013import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse; 014import com.nimbusds.oauth2.sdk.ParseException; 015import com.nimbusds.oauth2.sdk.ResponseType; 016import com.nimbusds.oauth2.sdk.SerializeException; 017import com.nimbusds.oauth2.sdk.id.State; 018import com.nimbusds.oauth2.sdk.http.HTTPResponse; 019import com.nimbusds.oauth2.sdk.token.AccessToken; 020import com.nimbusds.oauth2.sdk.util.URLUtils; 021 022 023/** 024 * OpenID Connect authorisation success response. Used to return an 025 * authorization code, access token and / or ID Token at the Authorisation 026 * endpoint. This class is immutable. 027 * 028 * <p>Example HTTP response with code and ID Token (code flow): 029 * 030 * <pre> 031 * HTTP/1.1 302 Found 032 * Location: https://client.example.org/cb# 033 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 034 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 035 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 036 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 037 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 038 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 039 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 040 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 041 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 042 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 043 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 044 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 045 * </pre> 046 * 047 * <p>Related specifications: 048 * 049 * <ul> 050 * <li>OpenID Connect Messages 1.0, section 2.1.2. 051 * <li>OpenID Connect Standard 1.0, section 2.3.5.1. 052 * </ul> 053 * 054 * @author Vladimir Dzhuvinov 055 */ 056@Immutable 057public class OIDCAuthorizationSuccessResponse 058 extends AuthorizationSuccessResponse 059 implements OIDCAuthorizationResponse { 060 061 062 /** 063 * The ID token, if requested. 064 */ 065 private final JWT idToken; 066 067 068 /** 069 * Creates a new OpenID Connect authorisation success response. 070 * 071 * @param redirectURI The requested redirect URI. Must not be 072 * {@code null}. 073 * @param code The authorisation code, {@code null} if not 074 * requested. 075 * @param idToken The ID token (ready for output), {@code null} if 076 * not requested. 077 * @param accessToken The UserInfo access token, {@code null} if not 078 * requested. 079 * @param state The state, {@code null} if not requested. 080 */ 081 public OIDCAuthorizationSuccessResponse(final URL redirectURI, 082 final AuthorizationCode code, 083 final JWT idToken, 084 final AccessToken accessToken, 085 final State state) { 086 087 super(redirectURI, code, accessToken, state); 088 089 this.idToken = idToken; 090 } 091 092 093 @Override 094 public ResponseType getImpliedResponseType() { 095 096 ResponseType rt = new ResponseType(); 097 098 if (getAuthorizationCode() != null) 099 rt.add(ResponseType.Value.CODE); 100 101 if (getIDToken() != null) 102 rt.add(OIDCResponseTypeValue.ID_TOKEN); 103 104 if (getAccessToken() != null) 105 rt.add(ResponseType.Value.TOKEN); 106 107 return rt; 108 } 109 110 111 /** 112 * Gets the requested ID token. 113 * 114 * @return The ID token (ready for output), {@code null} if not 115 * requested. 116 */ 117 public JWT getIDToken() { 118 119 return idToken; 120 } 121 122 123 @Override 124 public Map<String,String> toParameters() 125 throws SerializeException { 126 127 Map<String,String> params = super.toParameters(); 128 129 if (idToken != null) { 130 131 try { 132 133 params.put("id_token", idToken.serialize()); 134 135 } catch (IllegalStateException e) { 136 137 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 138 139 } 140 } 141 142 return params; 143 } 144 145 146 /** 147 * Parses an OpenID Connect authorisation success response from the 148 * specified redirect URI and parameters. 149 * 150 * @param redirectURI The base redirect URI. Must not be {@code null}. 151 * @param params The response parameters to parse. Must not be 152 * {@code null}. 153 * 154 * @return The OpenID Connect authorisation success response. 155 * 156 * @throws ParseException If the parameters couldn't be parsed to an 157 * OpenID Connect authorisation success 158 * response. 159 */ 160 public static OIDCAuthorizationSuccessResponse parse(final URL redirectURI, 161 final Map<String,String> params) 162 throws ParseException { 163 164 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 165 166 // Parse id_token parameter 167 168 JWT idToken = null; 169 170 if (params.get("id_token") != null) { 171 172 try { 173 idToken = JWTParser.parse(params.get("id_token")); 174 175 } catch (java.text.ParseException e) { 176 177 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 178 } 179 } 180 181 return new OIDCAuthorizationSuccessResponse(redirectURI, 182 asr.getAuthorizationCode(), 183 idToken, 184 asr.getAccessToken(), 185 asr.getState()); 186 } 187 188 189 /** 190 * Parses an OpenID Connect authorisation success response from the 191 * specified URI. 192 * 193 * <p>Example URI: 194 * 195 * <pre> 196 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 197 * </pre> 198 * 199 * @param uri The URI to parse. Can be absolute or relative, with a 200 * fragment or query string containing the authorisation 201 * response parameters. Must not be {@code null}. 202 * 203 * @return The OpenID Connect authorisation success response. 204 * 205 * @throws ParseException If the redirect URI couldn't be parsed to an 206 * OpenID Connect authorisation success 207 * response. 208 */ 209 public static OIDCAuthorizationSuccessResponse parse(final URL uri) 210 throws ParseException { 211 212 String paramString = null; 213 214 if (uri.getQuery() != null) 215 paramString = uri.getQuery(); 216 217 else if (uri.getRef() != null) 218 paramString = uri.getRef(); 219 220 else 221 throw new ParseException("Missing authorization response parameters"); 222 223 Map<String,String> params = URLUtils.parseParameters(paramString); 224 225 if (params == null) 226 throw new ParseException("Missing or invalid authorization response parameters"); 227 228 return parse(URLUtils.getBaseURL(uri), params); 229 } 230 231 232 /** 233 * Parses an OpenID Connect authorisation success response from the 234 * specified HTTP response. 235 * 236 * <p>Example HTTP response: 237 * 238 * <pre> 239 * HTTP/1.1 302 Found 240 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 241 * </pre> 242 * 243 * @param httpResponse The HTTP response to parse. Must not be 244 * {@code null}. 245 * 246 * @return The OpenID Connect authorisation success response. 247 * 248 * @throws ParseException If the HTTP response couldn't be parsed to an 249 * OpenID Connect authorisation success 250 * response. 251 */ 252 public static OIDCAuthorizationSuccessResponse parse(final HTTPResponse httpResponse) 253 throws ParseException { 254 255 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 256 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 257 httpResponse.getStatusCode()); 258 259 URL location = httpResponse.getLocation(); 260 261 if (location == null) 262 throw new ParseException("Missing redirect URL / HTTP Location header"); 263 264 return parse(location); 265 } 266}