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