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
155                Map<String,String> params = new HashMap<>();
156
157                if (code != null)
158                        params.put("code", code.getValue());
159
160                if (accessToken != null) {
161                        
162                        for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) {
163
164                                params.put(entry.getKey(), entry.getValue().toString());
165                        }
166                }
167                        
168                if (getState() != null)
169                        params.put("state", getState().getValue());
170
171                return params;
172        }
173
174
175        /**
176         * Parses an authorisation success response.
177         *
178         * @param redirectURI The base redirection URI. Must not be
179         *                    {@code null}.
180         * @param params      The response parameters to parse. Must not be 
181         *                    {@code null}.
182         *
183         * @return The authorisation success response.
184         *
185         * @throws ParseException If the parameters couldn't be parsed to an
186         *                        authorisation success response.
187         */
188        public static AuthorizationSuccessResponse parse(final URI redirectURI,
189                                                         final Map<String,String> params)
190                throws ParseException {
191        
192                // Parse code parameter
193                
194                AuthorizationCode code = null;
195                
196                if (params.get("code") != null) {
197                        code = new AuthorizationCode(params.get("code"));
198                }
199                
200                // Parse access_token parameters
201                
202                AccessToken accessToken = null;
203                
204                if (params.get("access_token") != null) {
205
206                        JSONObject jsonObject = new JSONObject();
207                        jsonObject.putAll(params);
208                        accessToken = AccessToken.parse(jsonObject);
209                }
210                
211                // Parse optional state parameter
212                State state = State.parse(params.get("state"));
213                
214                return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state, null);
215        }
216        
217        
218        /**
219         * Parses an authorisation success response.
220         *
221         * <p>Use a relative URI if the host, port and path details are not
222         * known:
223         *
224         * <pre>
225         * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj");
226         * </pre>
227         *
228         * <p>Example URI:
229         *
230         * <pre>
231         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
232         * </pre>
233         *
234         * @param uri The URI to parse. Can be absolute or relative, with a
235         *            fragment or query string containing the authorisation
236         *            response parameters. Must not be {@code null}.
237         *
238         * @return The authorisation success response.
239         *
240         * @throws ParseException If the redirection URI couldn't be parsed to
241         *                        an authorisation success response.
242         */
243        public static AuthorizationSuccessResponse parse(final URI uri)
244                throws ParseException {
245
246                Map<String,String> params;
247
248                if (uri.getRawFragment() != null) {
249
250                        params = URLUtils.parseParameters(uri.getRawFragment());
251
252                } else if (uri.getRawQuery() != null) {
253
254                        params = URLUtils.parseParameters(uri.getRawQuery());
255
256                } else {
257
258                        throw new ParseException("Missing URI fragment or query string");
259                }
260
261                return parse(URIUtils.getBaseURI(uri), params);
262        }
263
264
265        /**
266         * Parses an authorisation success response from the specified initial
267         * HTTP 302 redirect response generated at the authorisation endpoint.
268         *
269         * <p>Example HTTP response:
270         *
271         * <pre>
272         * HTTP/1.1 302 Found
273         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
274         * </pre>
275         *
276         * @see #parse(HTTPRequest)
277         *
278         * @param httpResponse The HTTP response to parse. Must not be 
279         *                     {@code null}.
280         *
281         * @return The authorisation success response.
282         *
283         * @throws ParseException If the HTTP response couldn't be parsed to an 
284         *                        authorisation success response.
285         */
286        public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
287                throws ParseException {
288                
289                URI location = httpResponse.getLocation();
290                
291                if (location == null) {
292                        throw new ParseException("Missing redirection URL / HTTP Location header");
293                }
294
295                return parse(location);
296        }
297
298
299        /**
300         * Parses an authorisation success response from the specified HTTP
301         * request at the client redirection (callback) URI. Applies to
302         * {@code query}, {@code fragment} and {@code form_post} response
303         * modes.
304         *
305         * <p>Example HTTP request (authorisation success):
306         *
307         * <pre>
308         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
309         * Host: client.example.com
310         * </pre>
311         *
312         * @see #parse(HTTPResponse)
313         *
314         * @param httpRequest The HTTP request to parse. Must not be
315         *                    {@code null}.
316         *
317         * @throws ParseException If the HTTP request couldn't be parsed to an
318         *                        authorisation success response.
319         */
320        public static AuthorizationSuccessResponse parse(final HTTPRequest httpRequest)
321                throws ParseException {
322
323                final URI baseURI;
324
325                try {
326                        baseURI = httpRequest.getURL().toURI();
327
328                } catch (URISyntaxException e) {
329                        throw new ParseException(e.getMessage(), e);
330                }
331
332                if (httpRequest.getQuery() != null) {
333                        // For query string and form_post response mode
334                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery()));
335                } else if (httpRequest.getFragment() != null) {
336                        // For fragment response mode (never available in actual HTTP request from browser)
337                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment()));
338                } else {
339                        throw new ParseException("Missing URI fragment, query string or post body");
340                }
341        }
342}