001package com.nimbusds.openid.connect.sdk;
002
003
004import java.net.URI;
005import java.net.URISyntaxException;
006import java.util.Map;
007
008import net.jcip.annotations.Immutable;
009
010import com.nimbusds.jwt.JWT;
011import com.nimbusds.jwt.JWTParser;
012
013import com.nimbusds.oauth2.sdk.AuthorizationCode;
014import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse;
015import com.nimbusds.oauth2.sdk.ParseException;
016import com.nimbusds.oauth2.sdk.ResponseType;
017import com.nimbusds.oauth2.sdk.SerializeException;
018import com.nimbusds.oauth2.sdk.id.State;
019import com.nimbusds.oauth2.sdk.http.HTTPResponse;
020import com.nimbusds.oauth2.sdk.token.AccessToken;
021import com.nimbusds.oauth2.sdk.util.URIUtils;
022import com.nimbusds.oauth2.sdk.util.URLUtils;
023import org.apache.commons.lang3.StringUtils;
024
025
026/**
027 * OpenID Connect authentication success response. Used to return an
028 * authorisation code, access token and / or ID Token at the Authorisation
029 * endpoint.
030 *
031 * <p>Example HTTP response with code and ID Token (code flow):
032 *
033 * <pre>
034 * HTTP/1.1 302 Found
035 * Location: https://client.example.org/cb#
036 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
037 * &amp;id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc
038 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI
039 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN
040 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM
041 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q
042 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT
043 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68
044 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9
045 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk
046 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac
047 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ
048 * </pre>
049 *
050 * <p>Related specifications:
051 *
052 * <ul>
053 *     <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6,
054 *         3.3.2.5 and 3.3.2.6.
055 *     <li>OpenID Connect Session Management 1.0 - draft 22, section 3.
056 * </ul>
057 */
058@Immutable
059public class AuthenticationSuccessResponse
060        extends AuthorizationSuccessResponse
061        implements AuthenticationResponse {
062
063
064        /**
065         * The ID token, if requested.
066         */
067        private final JWT idToken;
068
069
070        /**
071         * The session state, required if session management is supported.
072         */
073        private final State sessionState;
074        
075        
076        /**
077         * Creates a new OpenID Connect authentication success response.
078         *
079         * @param redirectURI The requested redirection URI. Must not be
080         *                    {@code null}.
081         * @param code        The authorisation code, {@code null} if not 
082         *                    requested.
083         * @param idToken     The ID token (ready for output), {@code null} if 
084         *                    not requested.
085         * @param accessToken The UserInfo access token, {@code null} if not 
086         *                    requested.
087         * @param state       The state, {@code null} if not requested.
088         */
089        public AuthenticationSuccessResponse(final URI redirectURI,
090                                             final AuthorizationCode code,
091                                             final JWT idToken,
092                                             final AccessToken accessToken,
093                                             final State state) {
094                
095                this(redirectURI, code, idToken, accessToken, state, null);
096        }
097
098
099        /**
100         * Creates a new OpenID Connect authentication success response.
101         *
102         * @param redirectURI  The requested redirection URI. Must not be
103         *                     {@code null}.
104         * @param code         The authorisation code, {@code null} if not
105         *                     requested.
106         * @param idToken      The ID token (ready for output), {@code null} if
107         *                     not requested.
108         * @param accessToken  The UserInfo access token, {@code null} if not
109         *                     requested.
110         * @param state        The state, {@code null} if not requested.
111         * @param sessionState The session store, {@code null} if session
112         *                     management is not supported.
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
121                super(redirectURI, code, accessToken, state);
122
123                this.idToken = idToken;
124
125                this.sessionState = sessionState;
126        }
127        
128        
129        @Override
130        public ResponseType impliedResponseType() {
131        
132                ResponseType rt = new ResponseType();
133                
134                if (getAuthorizationCode() != null) {
135                        rt.add(ResponseType.Value.CODE);
136                }
137
138                if (getIDToken() != null) {
139                        rt.add(OIDCResponseTypeValue.ID_TOKEN);
140                }
141                
142                if (getAccessToken() != null) {
143                        rt.add(ResponseType.Value.TOKEN);
144                }
145                        
146                return rt;
147        }
148        
149        
150        /**
151         * Gets the requested ID token.
152         *
153         * @return The ID token (ready for output), {@code null} if not 
154         *         requested.
155         */
156        public JWT getIDToken() {
157        
158                return idToken;
159        }
160
161
162        /**
163         * Gets the session state for session management.
164         *
165         * @return The session store, {@code null} if session management is not
166         *         supported.
167         */
168        public State getSessionState() {
169
170                return sessionState;
171        }
172        
173        
174        @Override
175        public Map<String,String> toParameters()
176                throws SerializeException {
177        
178                Map<String,String> params = super.toParameters();
179
180                if (idToken != null) {
181
182                        try {
183                                params.put("id_token", idToken.serialize());            
184                                
185                        } catch (IllegalStateException e) {
186                        
187                                throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e);
188                        
189                        }
190                }
191
192                if (sessionState != null) {
193
194                        params.put("session_state", sessionState.getValue());
195                }
196
197                return params;
198        }
199
200
201        @Override
202        public URI toURI()
203                throws SerializeException {
204
205                StringBuilder sb = new StringBuilder(getRedirectionURI().toString());
206
207                // Fragment or query string?
208                if (idToken != null || getAccessToken() != null) {
209                        sb.append('#');
210                } else {
211                        sb.append('?');
212                }
213
214                sb.append(URLUtils.serializeParameters(toParameters()));
215
216                try {
217                        return new URI(sb.toString());
218
219                } catch (URISyntaxException e) {
220
221                        throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e);
222                }
223        }
224
225
226        /**
227         * Parses an OpenID Connect authentication success response from the
228         * specified redirection URI and parameters.
229         *
230         * @param redirectURI The base redirection URI. Must not be
231         *                    {@code null}.
232         * @param params      The response parameters to parse. Must not be 
233         *                    {@code null}.
234         *
235         * @return The OpenID Connect authentication success response.
236         *
237         * @throws ParseException If the parameters couldn't be parsed to an
238         *                        OpenID Connect authentication success
239         *                        response.
240         */
241        public static AuthenticationSuccessResponse parse(final URI redirectURI,
242                                                          final Map<String,String> params)
243                throws ParseException {
244
245                AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params);
246
247                // Parse id_token parameter
248                JWT idToken = null;
249                
250                if (params.get("id_token") != null) {
251                        
252                        try {
253                                idToken = JWTParser.parse(params.get("id_token"));
254                                
255                        } catch (java.text.ParseException e) {
256                        
257                                throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e);
258                        }
259                }
260
261                // Parse the optional session_state parameter
262
263                State sessionState = null;
264
265                if (StringUtils.isNotBlank(params.get("session_state"))) {
266
267                        sessionState = new State(params.get("session_state"));
268                }
269
270                return new AuthenticationSuccessResponse(redirectURI,
271                                                            asr.getAuthorizationCode(),
272                                                            idToken,
273                                                            asr.getAccessToken(),
274                                                            asr.getState(),
275                                                            sessionState);
276        }
277        
278        
279        /**
280         * Parses an OpenID Connect authentication success response from the
281         * specified URI.
282         *
283         * <p>Example URI:
284         *
285         * <pre>
286         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
287         * </pre>
288         *
289         * @param uri The URI to parse. Can be absolute or relative, with a
290         *            fragment or query string containing the authentication
291         *            response parameters. Must not be {@code null}.
292         *
293         * @return The OpenID Connect authentication success response.
294         *
295         * @throws ParseException If the redirection URI couldn't be parsed to
296         *                        an OpenID Connect authentication success
297         *                        response.
298         */
299        public static AuthenticationSuccessResponse parse(final URI uri)
300                throws ParseException {
301                
302                String paramString;
303                
304                if (uri.getRawQuery() != null) {
305
306                        paramString = uri.getRawQuery();
307                                
308                } else if (uri.getRawFragment() != null) {
309
310                        paramString = uri.getRawFragment();
311                
312                } else {
313                        throw new ParseException("Missing authorization response parameters");
314                }
315                
316                Map<String,String> params = URLUtils.parseParameters(paramString);
317
318                if (params == null) {
319                        throw new ParseException("Missing or invalid authorization response parameters");
320                }
321
322                return parse(URIUtils.getBaseURI(uri), params);
323        }
324
325
326        /**
327         * Parses an OpenID Connect authentication success response from the
328         * specified HTTP response.
329         *
330         * <p>Example HTTP response:
331         *
332         * <pre>
333         * HTTP/1.1 302 Found
334         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
335         * </pre>
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                if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
350                        throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
351                                                 httpResponse.getStatusCode());
352                
353                URI location = httpResponse.getLocation();
354                
355                if (location == null)
356                        throw new ParseException("Missing redirection URI / HTTP Location header");
357
358                return parse(location);
359        }
360}