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