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