001    package com.nimbusds.oauth2.sdk;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    import java.util.HashMap;
007    import java.util.Map;
008    
009    import net.jcip.annotations.Immutable;
010    
011    import net.minidev.json.JSONObject;
012    
013    import com.nimbusds.oauth2.sdk.id.State;
014    import com.nimbusds.oauth2.sdk.token.AccessToken;
015    import com.nimbusds.oauth2.sdk.http.HTTPResponse;
016    import 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
047    public 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    }