001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.URI;
005import java.net.URISyntaxException;
006import java.util.HashMap;
007import java.util.Map;
008
009import net.jcip.annotations.Immutable;
010
011import net.minidev.json.JSONObject;
012
013import com.nimbusds.oauth2.sdk.id.State;
014import com.nimbusds.oauth2.sdk.token.AccessToken;
015import com.nimbusds.oauth2.sdk.http.HTTPRequest;
016import com.nimbusds.oauth2.sdk.http.HTTPResponse;
017import com.nimbusds.oauth2.sdk.util.URIUtils;
018import com.nimbusds.oauth2.sdk.util.URLUtils;
019
020
021/**
022 * Authorisation success response. Used to return an authorisation code or 
023 * access token at the Authorisation endpoint.
024 *
025 * <p>Example HTTP response with code (code flow):
026 *
027 * <pre>
028 * HTTP/1.1 302 Found
029 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
030 * </pre>
031 *
032 * <p>Example HTTP response with access token (implicit flow):
033 *
034 * <pre>
035 * HTTP/1.1 302 Found
036 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
037 *           &amp;state=xyz&amp;token_type=Bearer&amp;expires_in=3600
038 * </pre>
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2.
044 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
045 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
046 * </ul>
047 */
048@Immutable
049public class AuthorizationSuccessResponse 
050        extends AuthorizationResponse 
051        implements SuccessResponse {
052        
053        
054        /**
055         * The authorisation code, if requested.
056         */
057        private final AuthorizationCode code;
058        
059        
060        /**
061         * The access token, if requested.
062         */
063        private final AccessToken accessToken;
064        
065        
066        /**
067         * Creates a new authorisation success response.
068         *
069         * @param redirectURI The base redirection URI. Must not be
070         *                    {@code null}.
071         * @param code        The authorisation code, {@code null} if not 
072         *                    requested.
073         * @param accessToken The access token, {@code null} if not requested.
074         * @param state       The state, {@code null} if not specified.
075         * @param rm          The response mode, {@code null} if not specified.
076         */
077        public AuthorizationSuccessResponse(final URI redirectURI,
078                                            final AuthorizationCode code,
079                                            final AccessToken accessToken,
080                                            final State state,
081                                            final ResponseMode rm) {
082        
083                super(redirectURI, state, rm);
084                this.code = code;
085                this.accessToken = accessToken;
086        }
087
088
089        @Override
090        public boolean indicatesSuccess() {
091
092                return true;
093        }
094        
095        
096        /**
097         * Returns the implied response type.
098         *
099         * @return The implied response type.
100         */
101        public ResponseType impliedResponseType() {
102        
103                ResponseType rt = new ResponseType();
104                
105                if (code != null)
106                        rt.add(ResponseType.Value.CODE);
107                
108                if (accessToken != null)
109                        rt.add(ResponseType.Value.TOKEN);
110                        
111                return rt;
112        }
113
114
115        @Override
116        public ResponseMode impliedResponseMode() {
117
118                if (getResponseMode() != null) {
119                        return getResponseMode();
120                } else {
121                        if (accessToken != null) {
122                                return ResponseMode.FRAGMENT;
123                        } else {
124                                return ResponseMode.QUERY;
125                        }
126                }
127        }
128        
129        
130        /**
131         * Gets the authorisation code.
132         *
133         * @return The authorisation code, {@code null} if not requested.
134         */
135        public AuthorizationCode getAuthorizationCode() {
136        
137                return code;
138        }
139        
140        
141        /**
142         * Gets the access token.
143         *
144         * @return The access token, {@code null} if not requested.
145         */
146        public AccessToken getAccessToken() {
147        
148                return accessToken;
149        }
150
151
152        @Override
153        public Map<String,String> toParameters()
154                throws SerializeException {
155
156                Map<String,String> params = new HashMap<>();
157
158                if (code != null)
159                        params.put("code", code.getValue());
160
161                if (accessToken != null) {
162                        
163                        for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) {
164
165                                params.put(entry.getKey(), entry.getValue().toString());
166                        }
167                }
168                        
169                if (getState() != null)
170                        params.put("state", getState().getValue());
171
172                return params;
173        }
174
175
176        /**
177         * Parses an authorisation success response.
178         *
179         * @param redirectURI The base redirection URI. Must not be
180         *                    {@code null}.
181         * @param params      The response parameters to parse. Must not be 
182         *                    {@code null}.
183         *
184         * @return The authorisation success response.
185         *
186         * @throws ParseException If the parameters couldn't be parsed to an
187         *                        authorisation success response.
188         */
189        public static AuthorizationSuccessResponse parse(final URI redirectURI,
190                                                         final Map<String,String> params)
191                throws ParseException {
192        
193                // Parse code parameter
194                
195                AuthorizationCode code = null;
196                
197                if (params.get("code") != null) {
198                        code = new AuthorizationCode(params.get("code"));
199                }
200                
201                // Parse access_token parameters
202                
203                AccessToken accessToken = null;
204                
205                if (params.get("access_token") != null) {
206
207                        JSONObject jsonObject = new JSONObject();
208                        jsonObject.putAll(params);
209                        accessToken = AccessToken.parse(jsonObject);
210                }
211                
212                // Parse optional state parameter
213                State state = State.parse(params.get("state"));
214                
215                return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state, null);
216        }
217        
218        
219        /**
220         * Parses an authorisation success response.
221         *
222         * <p>Use a relative URI if the host, port and path details are not
223         * known:
224         *
225         * <pre>
226         * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj");
227         * </pre>
228         *
229         * <p>Example URI:
230         *
231         * <pre>
232         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
233         * </pre>
234         *
235         * @param uri The URI to parse. Can be absolute or relative, with a
236         *            fragment or query string containing the authorisation
237         *            response parameters. Must not be {@code null}.
238         *
239         * @return The authorisation success response.
240         *
241         * @throws ParseException If the redirection URI couldn't be parsed to
242         *                        an authorisation success response.
243         */
244        public static AuthorizationSuccessResponse parse(final URI uri)
245                throws ParseException {
246
247                Map<String,String> params;
248
249                if (uri.getRawFragment() != null) {
250
251                        params = URLUtils.parseParameters(uri.getRawFragment());
252
253                } else if (uri.getRawQuery() != null) {
254
255                        params = URLUtils.parseParameters(uri.getRawQuery());
256
257                } else {
258
259                        throw new ParseException("Missing URI fragment or query string");
260                }
261
262                return parse(URIUtils.getBaseURI(uri), params);
263        }
264
265
266        /**
267         * Parses an authorisation success response from the specified initial
268         * HTTP 302 redirect response generated at the authorisation endpoint.
269         *
270         * <p>Example HTTP response:
271         *
272         * <pre>
273         * HTTP/1.1 302 Found
274         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
275         * </pre>
276         *
277         * @see #parse(HTTPRequest)
278         *
279         * @param httpResponse The HTTP response to parse. Must not be 
280         *                     {@code null}.
281         *
282         * @return The authorisation success response.
283         *
284         * @throws ParseException If the HTTP response couldn't be parsed to an 
285         *                        authorisation success response.
286         */
287        public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
288                throws ParseException {
289                
290                URI location = httpResponse.getLocation();
291                
292                if (location == null) {
293                        throw new ParseException("Missing redirection URL / HTTP Location header");
294                }
295
296                return parse(location);
297        }
298
299
300        /**
301         * Parses an authorisation success response from the specified HTTP
302         * request at the client redirection (callback) URI. Applies to
303         * {@code query}, {@code fragment} and {@code form_post} response
304         * modes.
305         *
306         * <p>Example HTTP request (authorisation success):
307         *
308         * <pre>
309         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
310         * Host: client.example.com
311         * </pre>
312         *
313         * @see #parse(HTTPResponse)
314         *
315         * @param httpRequest The HTTP request to parse. Must not be
316         *                    {@code null}.
317         *
318         * @throws ParseException If the HTTP request couldn't be parsed to an
319         *                        authorisation success response.
320         */
321        public static AuthorizationSuccessResponse parse(final HTTPRequest httpRequest)
322                throws ParseException {
323
324                final URI baseURI;
325
326                try {
327                        baseURI = httpRequest.getURL().toURI();
328
329                } catch (URISyntaxException e) {
330                        throw new ParseException(e.getMessage(), e);
331                }
332
333                if (httpRequest.getQuery() != null) {
334                        // For query string and form_post response mode
335                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery()));
336                } else if (httpRequest.getFragment() != null) {
337                        // For fragment response mode (never available in actual HTTP request from browser)
338                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment()));
339                } else {
340                        throw new ParseException("Missing URI fragment, query string or post body");
341                }
342        }
343}