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