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