001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.URI;
005import java.net.URISyntaxException;
006import java.net.URL;
007import java.util.HashMap;
008import java.util.Map;
009
010import net.jcip.annotations.Immutable;
011
012import net.minidev.json.JSONObject;
013
014import com.nimbusds.oauth2.sdk.id.State;
015import com.nimbusds.oauth2.sdk.token.AccessToken;
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 * </ul>
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 URI 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 URI 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 URI redirectURI,
115                                            final AuthorizationCode code,
116                                            final AccessToken accessToken,
117                                            final State state) {
118        
119                super(redirectURI, state);
120                this.code = code;
121                this.accessToken = accessToken;
122        }
123        
124        
125        /**
126         * Returns the implied response type.
127         *
128         * @return The implied response type.
129         */
130        public ResponseType impliedResponseType() {
131        
132                ResponseType rt = new ResponseType();
133                
134                if (code != null)
135                        rt.add(ResponseType.Value.CODE);
136                
137                if (accessToken != null)
138                        rt.add(ResponseType.Value.TOKEN);
139                        
140                return rt;
141        }
142        
143        
144        /**
145         * Gets the authorisation code.
146         *
147         * @return The authorisation code, {@code null} if not requested.
148         */
149        public AuthorizationCode getAuthorizationCode() {
150        
151                return code;
152        }
153        
154        
155        /**
156         * Gets the access token.
157         *
158         * @return The access token, {@code null} if not requested.
159         */
160        public AccessToken getAccessToken() {
161        
162                return accessToken;
163        }
164
165
166        @Override
167        public Map<String,String> toParameters()
168                throws SerializeException {
169
170                Map<String,String> params = new HashMap<>();
171
172                if (code != null)
173                        params.put("code", code.getValue());
174
175                if (accessToken != null) {
176                        
177                        for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) {
178
179                                params.put(entry.getKey(), entry.getValue().toString());
180                        }
181                }
182                        
183                if (getState() != null)
184                        params.put("state", getState().getValue());
185
186                return params;
187        }
188        
189        
190        @Override
191        public URI toURI()
192                throws SerializeException {
193        
194                StringBuilder sb = new StringBuilder(getRedirectionURI().toString());
195                
196                // Fragment or query string?
197                if (accessToken != null) {
198                        sb.append('#');
199                } else {
200                        sb.append('?');
201                }
202                
203                sb.append(URLUtils.serializeParameters(toParameters()));
204
205                try {
206                        return new URI(sb.toString());
207                        
208                } catch (URISyntaxException 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 redirection URI. Must not be
219         *                    {@code null}.
220         * @param params      The response parameters to parse. Must not be 
221         *                    {@code null}.
222         *
223         * @return The authorisation success response.
224         *
225         * @throws ParseException If the parameters couldn't be parsed to an
226         *                        authorisation success response.
227         */
228        public static AuthorizationSuccessResponse parse(final URI redirectURI,
229                                                         final Map<String,String> params)
230                throws ParseException {
231        
232                // Parse code parameter
233                
234                AuthorizationCode code = null;
235                
236                if (params.get("code") != null)
237                        code = new AuthorizationCode(params.get("code"));
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                        jsonObject.putAll(params);
247                        accessToken = AccessToken.parse(jsonObject);
248                }
249                
250                // Parse optional state parameter
251                State state = State.parse(params.get("state"));
252                
253                return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state);
254        }
255        
256        
257        /**
258         * Parses an authorisation success response.
259         *
260         * <p>Example URI:
261         *
262         * <pre>
263         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
264         * </pre>
265         *
266         * @param uri The URI to parse. Can be absolute or relative, with a
267         *            fragment or query string containing the authorisation
268         *            response parameters. Must not be {@code null}.
269         *
270         * @return The authorisation success response.
271         *
272         * @throws ParseException If the redirection URI couldn't be parsed to
273         *                        an authorisation success response.
274         */
275        public static AuthorizationSuccessResponse parse(final URI uri)
276                throws ParseException {
277                
278                String paramString;
279                
280                if (uri.getQuery() != null) {
281
282                        paramString = uri.getRawQuery();
283
284                } else if (uri.getRawFragment() != null) {
285
286                        paramString = uri.getRawFragment();
287
288                } else {
289
290                        throw new ParseException("Missing authorization response parameters");
291                }
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(URIUtils.getBaseURI(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 redirection URL / HTTP Location header");
331
332                try {
333                        return parse(location.toURI());
334
335                } catch (URISyntaxException e) {
336
337                        throw new ParseException(e.getMessage(), e);
338                }
339        }
340}