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