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