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.HTTPResponse;
016import com.nimbusds.oauth2.sdk.util.URIUtils;
017import com.nimbusds.oauth2.sdk.util.URLUtils;
018
019
020/**
021 * Authorisation success response. Used to return an authorisation code or 
022 * access token at the Authorisation endpoint.
023 *
024 * <p>Example HTTP response with code (code flow):
025 *
026 * <pre>
027 * HTTP/1.1 302 Found
028 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
029 * </pre>
030 *
031 * <p>Example HTTP response with access token (implicit flow):
032 *
033 * <pre>
034 * HTTP/1.1 302 Found
035 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
036 *           &amp;state=xyz&amp;token_type=Bearer&amp;expires_in=3600
037 * </pre>
038 *
039 * <p>Related specifications:
040 *
041 * <ul>
042 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2.
043 * </ul>
044 */
045@Immutable
046public class AuthorizationSuccessResponse 
047        extends AuthorizationResponse 
048        implements SuccessResponse {
049        
050        
051        /**
052         * The authorisation code, if requested.
053         */
054        private final AuthorizationCode code;
055        
056        
057        /**
058         * The access token, if requested.
059         */
060        private final AccessToken accessToken;
061
062
063        /**
064         * Creates a new authorisation success response in the code flow 
065         * (authorisation code grant).
066         *
067         * @param redirectURI The base redirection URI. Must not be
068         *                    {@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 URI 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 redirection URI. Must not be
088         *                    {@code null}.
089         * @param accessToken The access token. Must not be {@code null}.
090         * @param state       The state, {@code null} if not requested.
091         */
092        public AuthorizationSuccessResponse(final URI redirectURI,
093                                            final AccessToken accessToken,
094                                            final State state) {
095        
096                this(redirectURI, null, accessToken, state);
097
098                if (accessToken == null)
099                        throw new IllegalArgumentException("The access token must not be null");
100        }
101        
102        
103        /**
104         * Creates a new authorisation success response.
105         *
106         * @param redirectURI The base redirection URI. Must not be
107         *                    {@code null}.
108         * @param code        The authorisation code, {@code null} if not 
109         *                    requested.
110         * @param accessToken The access token, {@code null} if not requested.
111         * @param state       The state, {@code null} if not requested.
112         */
113        public AuthorizationSuccessResponse(final URI redirectURI,
114                                            final AuthorizationCode code,
115                                            final AccessToken accessToken,
116                                            final State state) {
117        
118                super(redirectURI, state);
119                this.code = code;
120                this.accessToken = accessToken;
121        }
122        
123        
124        /**
125         * Returns the implied response type.
126         *
127         * @return The implied response type.
128         */
129        public ResponseType impliedResponseType() {
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<>();
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 URI toURI()
191                throws SerializeException {
192        
193                StringBuilder sb = new StringBuilder(getRedirectionURI().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                try {
205                        return new URI(sb.toString());
206                        
207                } catch (URISyntaxException e) {
208                
209                        throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e);
210                }
211        }
212
213
214        /**
215         * Parses an authorisation success response.
216         *
217         * @param redirectURI The base redirection URI. Must not be
218         *                    {@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 URI 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                // Parse access_token parameters
239                
240                AccessToken accessToken = null;
241                
242                if (params.get("access_token") != null) {
243
244                        JSONObject jsonObject = new JSONObject();
245                        jsonObject.putAll(params);
246                        accessToken = AccessToken.parse(jsonObject);
247                }
248                
249                // Parse optional state parameter
250                State state = State.parse(params.get("state"));
251                
252                return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state);
253        }
254        
255        
256        /**
257         * Parses an authorisation success response.
258         *
259         * <p>Example URI:
260         *
261         * <pre>
262         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
263         * </pre>
264         *
265         * @param uri The URI to parse. Can be absolute or relative, with a
266         *            fragment or query string containing the authorisation
267         *            response parameters. Must not be {@code null}.
268         *
269         * @return The authorisation success response.
270         *
271         * @throws ParseException If the redirection URI couldn't be parsed to
272         *                        an authorisation success response.
273         */
274        public static AuthorizationSuccessResponse parse(final URI uri)
275                throws ParseException {
276                
277                String paramString;
278                
279                if (uri.getQuery() != null) {
280
281                        paramString = uri.getRawQuery();
282
283                } else if (uri.getRawFragment() != null) {
284
285                        paramString = uri.getRawFragment();
286
287                } else {
288
289                        throw new ParseException("Missing authorization response parameters");
290                }
291                
292                Map<String,String> params = URLUtils.parseParameters(paramString);
293
294                if (params == null)
295                        throw new ParseException("Missing or invalid authorization response parameters");
296
297                return parse(URIUtils.getBaseURI(uri), params);
298        }
299
300
301        /**
302         * Parses an authorisation success response.
303         *
304         * <p>Example HTTP response:
305         *
306         * <pre>
307         * HTTP/1.1 302 Found
308         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
309         * </pre>
310         *
311         * @param httpResponse The HTTP response to parse. Must not be 
312         *                     {@code null}.
313         *
314         * @return The authorisation success response.
315         *
316         * @throws ParseException If the HTTP response couldn't be parsed to an 
317         *                        authorisation success response.
318         */
319        public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
320                throws ParseException {
321                
322                if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
323                        throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
324                                                 httpResponse.getStatusCode());
325                
326                URI location = httpResponse.getLocation();
327                
328                if (location == null)
329                        throw new ParseException("Missing redirection URL / HTTP Location header");
330
331                return parse(location);
332        }
333}