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