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