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. This class is immutable.
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 Messages 1.0, section 2.2.3.
052 *     <li>OpenID Connect Standard 1.0, section 3.1.2.
053 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.4 and 5.1.
054 * </ul>
055 *
056 * @author Vladimir Dzhuvinov
057 */
058@Immutable
059public final 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                super(accessToken, refreshToken);
102                
103                this.idToken = idToken;
104
105                idTokenString = null;
106        }
107
108
109        /**
110         * Creates a new OpenID Connect access token response.
111         *
112         * @param accessToken   The access token. Must not be {@code null}.
113         * @param refreshToken  Optional refresh token, {@code null} if none.
114         * @param idTokenString The ID token string. Must be {@code null} if
115         *                      the request grant type was not
116         *                      {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}.
117         */
118        public OIDCAccessTokenResponse(final AccessToken accessToken,
119                                       final RefreshToken refreshToken,
120                                       final String idTokenString) {
121
122                super(accessToken, refreshToken);
123
124                idToken = null;
125
126                this.idTokenString = idTokenString;
127        }
128        
129        
130        /**
131         * Gets the ID token.
132         *
133         * @return The ID token, {@code null} if none or if parsing to a JWT
134         *         failed.
135         */
136        public JWT getIDToken() {
137
138                if (idToken != null)
139                        return idToken;
140
141                if (idTokenString != null) {
142
143                        try {
144                                return JWTParser.parse(idTokenString);
145
146                        } catch (java.text.ParseException e) {
147
148                                return null;
149                        }
150                }
151
152                return null;
153        }
154
155
156        /**
157         * Gets the ID token string.
158         *
159         * @return The ID token string, {@code null} if none or if
160         *         serialisation to a string failed.
161         */
162        public String getIDTokenString() {
163
164                if (idTokenString != null)
165                        return idTokenString;
166
167                if (idToken != null) {
168
169                        // Reproduce originally parsed string if any
170                        if (idToken.getParsedString() != null)
171                                return idToken.getParsedString();
172
173                        try {
174                                return idToken.serialize();
175
176                        } catch(IllegalStateException e) {
177
178                                return null;
179                        }
180                }
181
182                return null;
183        }
184        
185        
186        /**
187         * Returns the JSON object representing this OpenID Connect access 
188         * token response.
189         *
190         * <p>Example JSON object:
191         *
192         * <pre>
193         * {
194         *   "access_token" : "SlAV32hkKG",
195         *   "token_type"   : "Bearer",
196         *   "refresh_token": "8xLOxBtZp8",
197         *   "expires_in"   : 3600,
198         *   "id_token"     : "eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso"
199         * }
200         * </pre>
201         *
202         * @return The JSON object.
203         *
204         * @throws SerializeException If this OpenID Connect access token
205         *                            response couldn't be serialised to a JSON
206         *                            object.
207         */
208        @Override
209        public JSONObject toJSONObject()
210                throws SerializeException {
211        
212                JSONObject o = super.toJSONObject();
213
214                String idTokenOut = getIDTokenString();
215
216                if (idTokenOut != null)
217                        o.put("id_token", idTokenOut);
218                
219                return o;
220        }
221        
222        
223        /**
224         * Parses an OpenID Connect access token response from the specified 
225         * JSON object.
226         *
227         * @param jsonObject The JSON object to parse. Must not be 
228         *                   {@code null}.
229         *
230         * @return The OpenID Connect access token response.
231         *
232         * @throws ParseException If the JSON object couldn't be parsed to an
233         *                        OpenID Connect access token response.
234         */
235        public static OIDCAccessTokenResponse parse(final JSONObject jsonObject)
236                throws ParseException {
237                
238                AccessTokenResponse atr = AccessTokenResponse.parse(jsonObject);
239                
240                JWT idToken = null;
241                
242                if (jsonObject.containsKey("id_token")) {
243                        
244                        try {
245                                idToken = JWTParser.parse(JSONObjectUtils.getString(jsonObject, "id_token"));
246                                
247                        } catch (java.text.ParseException e) {
248                        
249                                throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e);
250                        }
251                }
252                
253                
254                return new OIDCAccessTokenResponse(atr.getAccessToken(),
255                                                   atr.getRefreshToken(),
256                                                   idToken);
257        }
258        
259        
260        /**
261         * Parses an OpenID Connect access token response from the specified 
262         * HTTP response.
263         *
264         * @param httpResponse The HTTP response. Must not be {@code null}.
265         *
266         * @return The OpenID Connect access token response.
267         *
268         * @throws ParseException If the HTTP response couldn't be parsed to an 
269         *                        OpenID Connect access token response.
270         */
271        public static OIDCAccessTokenResponse parse(final HTTPResponse httpResponse)
272                throws ParseException {
273                
274                httpResponse.ensureStatusCode(HTTPResponse.SC_OK);
275                
276                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
277                
278                return parse(jsonObject);
279        }
280}