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