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