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