001package com.nimbusds.openid.connect.sdk;
002
003
004import java.util.HashMap;
005import java.util.Map;
006
007import net.jcip.annotations.Immutable;
008
009import net.minidev.json.JSONObject;
010
011import com.nimbusds.jwt.JWT;
012import com.nimbusds.jwt.JWTParser;
013
014import com.nimbusds.oauth2.sdk.AccessTokenResponse;
015import com.nimbusds.oauth2.sdk.ParseException;
016import com.nimbusds.oauth2.sdk.SerializeException;
017import com.nimbusds.oauth2.sdk.http.HTTPResponse;
018import com.nimbusds.oauth2.sdk.token.AccessToken;
019import com.nimbusds.oauth2.sdk.token.RefreshToken;
020import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
021
022
023/**
024 * OpenID Connect access token response with an optional ID token.
025 *
026 * <p>Example HTTP response:
027 *
028 * <pre>
029 * HTTP/1.1 200 OK
030 * Content-Type: application/json
031 * Cache-Control: no-store
032 * Pragma: no-cache
033 * 
034 * {
035 *   "access_token"  : "SlAV32hkKG",
036 *   "token_type"    : "Bearer",
037 *   "refresh_token" : "8xLOxBtZp8",
038 *   "expires_in"    : 3600,
039 *   "id_token"      : "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9zZXJ2Z
040 *    XIuZXhhbXBsZS5jb20iLCJ1c2VyX2lkIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoic
041 *    zZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiZXhwIjoxMzExMjgxO
042 *    TcwLCJpYXQiOjEzMTEyODA5NzB9.RgXxzppVvn1EjUiV3LIZ19SyhdyREe_2jJjW
043 *    5EC8XjNuJfe7Dte8YxRXxssJ67N8MT9mvOI3HOHm4whNx5FCyemyCGyTLHODCeAr
044 *    _id029-4JP0KWySoan1jmT7vbGHhu89-l9MTdaEvu7pNZO7DHGwqnMWRe8hdG7jU
045 *    ES4w4ReQTygKwXVVOaiGoeUrv6cZdbyOnpGlRlHaiOsv_xMunNVJtn5dLz-0zZwV
046 *    ftKVpFuc1pGaVsyZsOtkT32E4c6MDHeCvIDlR5ESC0ct8BLvGJDB5954MjCR4_X2
047 *    GAEHonKw4NF8wTmUFvhslYXmjRNFs21Byjn3jNb7lSa3MBfVsw"
048 * }
049 * </pre>
050 *
051 * <p>Related specifications:
052 *
053 * <ul>
054 *     <li>OpenID Connect Core 1.0, section 3.1.3.3.
055 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.4 and 5.1.
056 * </ul>
057 */
058@Immutable
059public class OIDCAccessTokenResponse
060        extends AccessTokenResponse {
061
062
063        /**
064         * Optional ID Token serialised to a JWT.
065         */
066        private final JWT idToken;
067
068
069        /**
070         * Optional ID Token as raw string (for more efficient serialisation).
071         */
072        private final String idTokenString;
073
074
075        /**
076         * Creates a new OpenID Connect access token response with no ID token.
077         *
078         * @param accessToken  The access token. Must not be {@code null}.
079         * @param refreshToken Optional refresh token, {@code null} if none.
080         */
081        public OIDCAccessTokenResponse(final AccessToken accessToken,
082                                       final RefreshToken refreshToken) {
083
084                this(accessToken, refreshToken, (String)null);
085        }
086
087
088        /**
089         * Creates a new OpenID Connect access token response.
090         *
091         * @param accessToken  The access token. Must not be {@code null}.
092         * @param refreshToken Optional refresh token, {@code null} if none.
093         * @param idToken      The ID token. Must be {@code null} if the
094         *                     request grant type was not 
095         *                     {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}.
096         */
097        public OIDCAccessTokenResponse(final AccessToken accessToken,
098                                       final RefreshToken refreshToken,
099                                       final JWT idToken) {
100                                   
101                this(accessToken, refreshToken, idToken, null);
102        }
103
104
105        /**
106         * Creates a new OpenID Connect access token response.
107         *
108         * @param accessToken  The access token. Must not be {@code null}.
109         * @param refreshToken Optional refresh token, {@code null} if none.
110         * @param idToken      The ID token. Must be {@code null} if the
111         *                     request grant type was not
112         *                     {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}.
113         * @param customParams Optional custom parameters, {@code null} if
114         *                     none.
115         */
116        public OIDCAccessTokenResponse(final AccessToken accessToken,
117                                       final RefreshToken refreshToken,
118                                       final JWT idToken,
119                                       final Map<String,Object> customParams) {
120
121                super(accessToken, refreshToken, customParams);
122
123                this.idToken = idToken;
124
125                idTokenString = null;
126        }
127
128
129        /**
130         * Creates a new OpenID Connect access token response.
131         *
132         * @param accessToken   The access token. Must not be {@code null}.
133         * @param refreshToken  Optional refresh token, {@code null} if none.
134         * @param idTokenString The ID token string. Must be {@code null} if
135         *                      the request grant type was not
136         *                      {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}.
137         */
138        public OIDCAccessTokenResponse(final AccessToken accessToken,
139                                       final RefreshToken refreshToken,
140                                       final String idTokenString) {
141
142                this(accessToken, refreshToken, idTokenString, null);
143        }
144
145
146        /**
147         * Creates a new OpenID Connect access token response.
148         *
149         * @param accessToken   The access token. Must not be {@code null}.
150         * @param refreshToken  Optional refresh token, {@code null} if none.
151         * @param idTokenString The ID token string. Must be {@code null} if
152         *                      the request grant type was not
153         *                      {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}.
154         * @param customParams Optional custom parameters, {@code null} if
155         *                     none.
156         */
157        public OIDCAccessTokenResponse(final AccessToken accessToken,
158                                       final RefreshToken refreshToken,
159                                       final String idTokenString,
160                                       final Map<String,Object> customParams) {
161
162                super(accessToken, refreshToken, customParams);
163
164                idToken = null;
165
166                this.idTokenString = idTokenString;
167        }
168        
169        
170        /**
171         * Gets the ID token.
172         *
173         * @return The ID token, {@code null} if none or if parsing to a JWT
174         *         failed.
175         */
176        public JWT getIDToken() {
177
178                if (idToken != null)
179                        return idToken;
180
181                if (idTokenString != null) {
182
183                        try {
184                                return JWTParser.parse(idTokenString);
185
186                        } catch (java.text.ParseException e) {
187
188                                return null;
189                        }
190                }
191
192                return null;
193        }
194
195
196        /**
197         * Gets the ID token string.
198         *
199         * @return The ID token string, {@code null} if none or if
200         *         serialisation to a string failed.
201         */
202        public String getIDTokenString() {
203
204                if (idTokenString != null)
205                        return idTokenString;
206
207                if (idToken != null) {
208
209                        // Reproduce originally parsed string if any
210                        if (idToken.getParsedString() != null)
211                                return idToken.getParsedString();
212
213                        try {
214                                return idToken.serialize();
215
216                        } catch(IllegalStateException e) {
217
218                                return null;
219                        }
220                }
221
222                return null;
223        }
224        
225        
226        /**
227         * Returns the JSON object representing this OpenID Connect access 
228         * token response.
229         *
230         * <p>Example JSON object:
231         *
232         * <pre>
233         * {
234         *   "access_token" : "SlAV32hkKG",
235         *   "token_type"   : "Bearer",
236         *   "refresh_token": "8xLOxBtZp8",
237         *   "expires_in"   : 3600,
238         *   "id_token"     : "eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso"
239         * }
240         * </pre>
241         *
242         * @return The JSON object.
243         *
244         * @throws SerializeException If this OpenID Connect access token
245         *                            response couldn't be serialised to a JSON
246         *                            object.
247         */
248        @Override
249        public JSONObject toJSONObject()
250                throws SerializeException {
251        
252                JSONObject o = super.toJSONObject();
253
254                String idTokenOut = getIDTokenString();
255
256                if (idTokenOut != null)
257                        o.put("id_token", idTokenOut);
258                
259                return o;
260        }
261        
262        
263        /**
264         * Parses an OpenID Connect access token response from the specified 
265         * JSON object.
266         *
267         * @param jsonObject The JSON object to parse. Must not be 
268         *                   {@code null}.
269         *
270         * @return The OpenID Connect access token response.
271         *
272         * @throws ParseException If the JSON object couldn't be parsed to an
273         *                        OpenID Connect access token response.
274         */
275        public static OIDCAccessTokenResponse parse(final JSONObject jsonObject)
276                throws ParseException {
277                
278                AccessTokenResponse atr = AccessTokenResponse.parse(jsonObject);
279                
280                JWT idToken = null;
281                
282                if (jsonObject.containsKey("id_token")) {
283                        
284                        try {
285                                idToken = JWTParser.parse(JSONObjectUtils.getString(jsonObject, "id_token"));
286                                
287                        } catch (java.text.ParseException e) {
288                        
289                                throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e);
290                        }
291                }
292
293                // Parse the custom parameters
294                Map<String,Object> customParams = new HashMap<>();
295                customParams.putAll(atr.getCustomParams());
296                customParams.remove("id_token");
297                
298                return new OIDCAccessTokenResponse(atr.getAccessToken(),
299                                                   atr.getRefreshToken(),
300                                                   idToken,
301                                                   customParams);
302        }
303        
304        
305        /**
306         * Parses an OpenID Connect access token response from the specified 
307         * HTTP response.
308         *
309         * @param httpResponse The HTTP response. Must not be {@code null}.
310         *
311         * @return The OpenID Connect access token response.
312         *
313         * @throws ParseException If the HTTP response couldn't be parsed to an 
314         *                        OpenID Connect access token response.
315         */
316        public static OIDCAccessTokenResponse parse(final HTTPResponse httpResponse)
317                throws ParseException {
318                
319                httpResponse.ensureStatusCode(HTTPResponse.SC_OK);
320                
321                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
322                
323                return parse(jsonObject);
324        }
325}