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