001package com.nimbusds.oauth2.sdk;
002
003
004import java.util.Collections;
005import java.util.HashMap;
006import java.util.Map;
007import java.util.Set;
008
009import net.jcip.annotations.Immutable;
010
011import net.minidev.json.JSONObject;
012
013import com.nimbusds.oauth2.sdk.token.AccessToken;
014import com.nimbusds.oauth2.sdk.token.RefreshToken;
015import com.nimbusds.oauth2.sdk.token.TokenPair;
016import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
017import com.nimbusds.oauth2.sdk.http.HTTPResponse;
018
019
020/**
021 * Access token response from the Token endpoint.
022 *
023 * <p>Example HTTP response:
024 *
025 * <pre>
026 * HTTP/1.1 200 OK
027 * Content-Type: application/json;charset=UTF-8
028 * Cache-Control: no-store
029 * Pragma: no-cache
030 *
031 * {
032 *   "access_token"      : "2YotnFZFEjr1zCsicMWpAA",
033 *   "token_type"        : "example",
034 *   "expires_in"        : 3600,
035 *   "refresh_token"     : "tGzv3JOkF0XG5Qx2TlKWIA",
036 *   "example_parameter" : "example_value"
037 * }
038 * </pre>
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.4, 4.3.3,  4.4.3 and 5.1.
044 * </ul>
045 */
046@Immutable
047public class AccessTokenResponse 
048        extends TokenResponse
049        implements SuccessResponse {
050
051
052        /**
053         * The access token.
054         */
055        private final AccessToken accessToken;
056        
057        
058        /**
059         * Optional refresh token.
060         */
061        private final RefreshToken refreshToken;
062
063
064        /**
065         * Optional custom parameters.
066         */
067        private final Map<String,Object> customParams;
068        
069        
070        /**
071         * Creates a new access token response.
072         *
073         * @param accessToken  The access token. Must not be {@code null}.
074         * @param refreshToken Optional refresh token, {@code null} if none.
075         */
076        public AccessTokenResponse(final AccessToken accessToken,
077                                   final RefreshToken refreshToken) {
078                                   
079                this(accessToken, refreshToken, null);
080        }
081
082
083        /**
084         * Creates a new access token response.
085         *
086         * @param accessToken  The access token. Must not be {@code null}.
087         * @param refreshToken Optional refresh token, {@code null} if none.
088         * @param customParams Optional custom parameters, {@code null} if
089         *                     none.
090         */
091        public AccessTokenResponse(final AccessToken accessToken,
092                                   final RefreshToken refreshToken,
093                                   final Map<String,Object> customParams) {
094
095                if (accessToken == null)
096                        throw new IllegalArgumentException("The access token must not be null");
097
098                this.accessToken = accessToken;
099
100                this.refreshToken = refreshToken;
101
102                this.customParams = customParams;
103        }
104
105
106        /**
107         * Creates a new access token response.
108         *
109         * @param tokenPair The access and refresh token pair. Must not be 
110         *                  {@code null}.
111         */
112        public AccessTokenResponse(final TokenPair tokenPair) {
113                                   
114                this(tokenPair, null);
115        }
116
117
118        /**
119         * Creates a new access token response.
120         *
121         * @param tokenPair    The access and refresh token pair. Must not be
122         *                     {@code null}.
123         * @param customParams Optional custom parameters, {@code null} if
124         *                     none.
125         */
126        public AccessTokenResponse(final TokenPair tokenPair,
127                                   final Map<String,Object> customParams) {
128
129                this(tokenPair.getAccessToken(), tokenPair.getRefreshToken(), customParams);
130        }
131
132
133        @Override
134        public boolean indicatesSuccess() {
135
136                return true;
137        }
138        
139        
140        /**
141         * Gets the access token.
142         *
143         * @return The access token.
144         */
145        public AccessToken getAccessToken() {
146        
147                return accessToken;
148        }
149        
150        
151        /**
152         * Gets the optional refresh token.
153         *
154         * @return The refresh token, {@code null} if none.
155         */
156        public RefreshToken getRefreshToken() {
157        
158                return refreshToken;
159        }
160
161
162        /**
163         * Gets the access and refresh token pair.
164         *
165         * @return The access and refresh token pair. Must not be {@code null}.
166         */
167        public TokenPair getTokenPair() {
168
169                return new TokenPair(accessToken, refreshToken);
170        }
171
172
173        /**
174         * Gets the custom parameters.
175         *
176         * @return The custom parameters, as a unmodifiable map, empty map if
177         *         none.
178         */
179        public Map<String,Object> getCustomParams() {
180
181                if (customParams == null)
182                        return Collections.emptyMap();
183
184                return Collections.unmodifiableMap(customParams);
185        }
186        
187        
188        /**
189         * Returns the JSON object representing this access token response.
190         *
191         * <p>Example JSON object:
192         *
193         * <pre>
194         * {
195         *   "access_token" : "SlAV32hkKG",
196         *   "token_type"   : "Bearer",
197         *   "refresh_token": "8xLOxBtZp8",
198         *   "expires_in"   : 3600
199         * }
200         * </pre>
201         *
202         * @return The JSON object.
203         *
204         * @throws SerializeException If this access token response couldn't be
205         *                            serialised to a JSON object.
206         */
207        public JSONObject toJSONObject()
208                throws SerializeException {
209        
210                JSONObject o = accessToken.toJSONObject();
211
212                if (refreshToken != null)
213                        o.putAll(refreshToken.toJSONObject());
214
215                if (customParams != null)
216                        o.putAll(customParams);
217                
218                return o;
219        }
220        
221        
222        @Override
223        public HTTPResponse toHTTPResponse()
224                throws SerializeException {
225        
226                HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK);
227                
228                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
229                httpResponse.setCacheControl("no-store");
230                httpResponse.setPragma("no-cache");
231                
232                httpResponse.setContent(toJSONObject().toString());
233                
234                return httpResponse;
235        }
236        
237        
238        /**
239         * Parses an access token response from the specified JSON object.
240         *
241         * @param jsonObject The JSON object to parse. Must not be {@code null}.
242         *
243         * @return The access token response.
244         *
245         * @throws ParseException If the JSON object couldn't be parsed to an
246         *                        access token response.
247         */
248        public static AccessTokenResponse parse(final JSONObject jsonObject)
249                throws ParseException {
250                
251                AccessToken accessToken = AccessToken.parse(jsonObject);
252                
253                RefreshToken refreshToken = RefreshToken.parse(jsonObject);
254
255                // Get the std param names for the access + refresh token
256                Set<String> paramNames = accessToken.getParamNames();
257
258                if (refreshToken != null)
259                        paramNames.addAll(refreshToken.getParamNames());
260
261                // Determine the custom param names
262                Set<String> customParamNames = jsonObject.keySet();
263                customParamNames.removeAll(paramNames);
264
265                Map<String,Object> customParams = null;
266
267                if (customParamNames.size() > 0) {
268
269                        customParams = new HashMap<>();
270
271                        for (String name: customParamNames) {
272                                customParams.put(name, jsonObject.get(name));
273                        }
274                }
275                
276                return new AccessTokenResponse(accessToken, refreshToken, customParams);
277        }
278        
279        
280        /**
281         * Parses an access token response from the specified HTTP response.
282         *
283         * @param httpResponse The HTTP response. Must not be {@code null}.
284         *
285         * @return The access token response.
286         *
287         * @throws ParseException If the HTTP response couldn't be parsed to an 
288         *                        access token response.
289         */
290        public static AccessTokenResponse parse(final HTTPResponse httpResponse)
291                throws ParseException {
292                
293                httpResponse.ensureStatusCode(HTTPResponse.SC_OK);
294                
295                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
296                
297                return parse(jsonObject);
298        }
299}