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