001package com.nimbusds.openid.connect.sdk;
002
003
004import java.net.URL;
005import java.util.Map;
006
007import net.jcip.annotations.Immutable;
008
009import com.nimbusds.jwt.JWT;
010import com.nimbusds.jwt.JWTParser;
011
012import com.nimbusds.oauth2.sdk.AuthorizationCode;
013import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse;
014import com.nimbusds.oauth2.sdk.ParseException;
015import com.nimbusds.oauth2.sdk.ResponseType;
016import com.nimbusds.oauth2.sdk.SerializeException;
017import com.nimbusds.oauth2.sdk.id.State;
018import com.nimbusds.oauth2.sdk.http.HTTPResponse;
019import com.nimbusds.oauth2.sdk.token.AccessToken;
020import com.nimbusds.oauth2.sdk.util.URLUtils;
021
022
023/**
024 * OpenID Connect authorisation success response. Used to return an 
025 * authorization code, access token and / or ID Token at the Authorisation 
026 * endpoint. This class is immutable. 
027 *
028 * <p>Example HTTP response with code and ID Token (code flow):
029 *
030 * <pre>
031 * HTTP/1.1 302 Found
032 * Location: https://client.example.org/cb#
033 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
034 * &amp;id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc
035 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI
036 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN
037 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM
038 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q
039 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT
040 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68
041 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9
042 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk
043 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac
044 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ
045 * </pre>
046 *
047 * <p>Related specifications:
048 *
049 * <ul>
050 *     <li>OpenID Connect Messages 1.0, section 2.1.2.
051 *     <li>OpenID Connect Standard 1.0, section 2.3.5.1.
052 * </ul>
053 *
054 * @author Vladimir Dzhuvinov
055 */
056@Immutable
057public class OIDCAuthorizationSuccessResponse 
058        extends AuthorizationSuccessResponse
059        implements OIDCAuthorizationResponse {
060
061
062        /**
063         * The ID token, if requested.
064         */
065        private final JWT idToken;
066        
067        
068        /**
069         * Creates a new OpenID Connect authorisation success response.
070         *
071         * @param redirectURI The requested redirect 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 OIDCAuthorizationSuccessResponse(final URL 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 getImpliedResponseType() {
095        
096                ResponseType rt = new ResponseType();
097                
098                if (getAuthorizationCode() != null)
099                        rt.add(ResponseType.Value.CODE);
100                        
101                if (getIDToken() != null)
102                        rt.add(OIDCResponseTypeValue.ID_TOKEN);
103                
104                if (getAccessToken() != null)
105                        rt.add(ResponseType.Value.TOKEN);
106                        
107                return rt;
108        }
109        
110        
111        /**
112         * Gets the requested ID token.
113         *
114         * @return The ID token (ready for output), {@code null} if not 
115         *         requested.
116         */
117        public JWT getIDToken() {
118        
119                return idToken;
120        }
121        
122        
123        @Override
124        public Map<String,String> toParameters()
125                throws SerializeException {
126        
127                Map<String,String> params = super.toParameters();
128
129                if (idToken != null) {
130
131                        try {
132                
133                                params.put("id_token", idToken.serialize());            
134                                
135                        } catch (IllegalStateException e) {
136                        
137                                throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e);
138                        
139                        }
140                }
141
142                return params;
143        }
144
145
146        /**
147         * Parses an OpenID Connect authorisation success response from the
148         * specified redirect URI and parameters.
149         *
150         * @param redirectURI The base redirect URI. Must not be {@code null}.
151         * @param params      The response parameters to parse. Must not be 
152         *                    {@code null}.
153         *
154         * @return The OpenID Connect authorisation success response.
155         *
156         * @throws ParseException If the parameters couldn't be parsed to an
157         *                        OpenID Connect authorisation success 
158         *                        response.
159         */
160        public static OIDCAuthorizationSuccessResponse parse(final URL redirectURI, 
161                                                             final Map<String,String> params)
162                throws ParseException {
163
164                AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params);
165
166                // Parse id_token parameter
167                
168                JWT idToken = null;
169                
170                if (params.get("id_token") != null) {
171                        
172                        try {
173                                idToken = JWTParser.parse(params.get("id_token"));
174                                
175                        } catch (java.text.ParseException e) {
176                        
177                                throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e);
178                        }
179                }
180
181                return new OIDCAuthorizationSuccessResponse(redirectURI,
182                                                            asr.getAuthorizationCode(),
183                                                            idToken,
184                                                            asr.getAccessToken(),
185                                                            asr.getState());
186        }
187        
188        
189        /**
190         * Parses an OpenID Connect authorisation success response from the
191         * specified URI.
192         *
193         * <p>Example URI:
194         *
195         * <pre>
196         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
197         * </pre>
198         *
199         * @param uri The URI to parse. Can be absolute or relative, with a
200         *            fragment or query string containing the authorisation
201         *            response parameters. Must not be {@code null}.
202         *
203         * @return The OpenID Connect authorisation success response.
204         *
205         * @throws ParseException If the redirect URI couldn't be parsed to an
206         *                        OpenID Connect authorisation success 
207         *                        response.
208         */
209        public static OIDCAuthorizationSuccessResponse parse(final URL uri)
210                throws ParseException {
211                
212                String paramString = null;
213                
214                if (uri.getQuery() != null)
215                        paramString = uri.getQuery();
216                                
217                else if (uri.getRef() != null)
218                        paramString = uri.getRef();
219                
220                else
221                        throw new ParseException("Missing authorization response parameters");
222                
223                Map<String,String> params = URLUtils.parseParameters(paramString);
224
225                if (params == null)
226                        throw new ParseException("Missing or invalid authorization response parameters");
227
228                return parse(URLUtils.getBaseURL(uri), params);
229        }
230
231
232        /**
233         * Parses an OpenID Connect authorisation success response from the
234         * specified HTTP response.
235         *
236         * <p>Example HTTP response:
237         *
238         * <pre>
239         * HTTP/1.1 302 Found
240         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
241         * </pre>
242         *
243         * @param httpResponse The HTTP response to parse. Must not be 
244         *                     {@code null}.
245         *
246         * @return The OpenID Connect authorisation success response.
247         *
248         * @throws ParseException If the HTTP response couldn't be parsed to an 
249         *                        OpenID Connect authorisation success 
250         *                        response.
251         */
252        public static OIDCAuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
253                throws ParseException {
254                
255                if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
256                        throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
257                                                 httpResponse.getStatusCode());
258                
259                URL location = httpResponse.getLocation();
260                
261                if (location == null)
262                        throw new ParseException("Missing redirect URL / HTTP Location header");
263                
264                return parse(location);
265        }
266}