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