001    package com.nimbusds.oauth2.sdk;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    
007    import java.util.HashMap;
008    import java.util.Map;
009    
010    import net.jcip.annotations.Immutable;
011    
012    import net.minidev.json.JSONObject;
013    
014    import com.nimbusds.oauth2.sdk.id.State;
015    
016    import com.nimbusds.oauth2.sdk.token.AccessToken;
017    
018    import com.nimbusds.oauth2.sdk.http.HTTPResponse;
019    
020    import com.nimbusds.oauth2.sdk.util.URLUtils;
021    
022    
023    /**
024     * Authorisation success response. Used to return an authorization code or 
025     * access token at the Authorisation endpoint. This class is immutable.
026     *
027     * <p>Example HTTP response with code (code flow):
028     *
029     * <pre>
030     * HTTP/1.1 302 Found
031     * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
032     * </pre>
033     *
034     * <p>Example HTTP response with access token (implicit flow):
035     *
036     * <pre>
037     * HTTP/1.1 302 Found
038     * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
039     *           &amp;state=xyz&amp;token_type=Bearer&amp;expires_in=3600
040     * </pre>
041     *
042     * <p>Related specifications:
043     *
044     * <ul>
045     *     <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2.
046     * </ul>
047     *
048     * @author Vladimir Dzhuvinov
049     * @version $version$ (2013-01-28)
050     */
051    @Immutable
052    public class AuthorizationSuccessResponse 
053            extends AuthorizationResponse 
054            implements SuccessResponse {
055            
056            
057            /**
058             * The authorisation code, if requested.
059             */
060            private final AuthorizationCode code;
061            
062            
063            /**
064             * The access token, if requested.
065             */
066            private final AccessToken accessToken;
067    
068    
069            /**
070             * Creates a new authorisation success response in the code flow 
071             * (authorisation code grant).
072             *
073             * @param redirectURI The base redirect URI. Must not be {@code null}.
074             * @param code        The authorisation code. Must not be {@code null}.
075             * @param state       The state, {@code null} if not requested.
076             */
077            public AuthorizationSuccessResponse(final URL redirectURI,
078                                                final AuthorizationCode code,
079                                                final State state) {
080            
081                    this(redirectURI, code, null, state);
082    
083                    if (code == null)
084                            throw new IllegalArgumentException("The authorization code must not be null");
085            }
086    
087    
088            /**
089             * Creates a new authorisation success response in the implicit flow 
090             * (implicit grant).
091             *
092             * @param redirectURI The base redirect URI. Must not be {@code null}.
093             * @param accessToken The access token. Must not be {@code null}.
094             * @param state       The state, {@code null} if not requested.
095             */
096            public AuthorizationSuccessResponse(final URL redirectURI,
097                                                final AccessToken accessToken,
098                                                final State state) {
099            
100                    this(redirectURI, null, accessToken, state);
101    
102                    if (accessToken == null)
103                            throw new IllegalArgumentException("The access token must not be null");
104            }
105            
106            
107            /**
108             * Creates a new authorisation success response.
109             *
110             * @param redirectURI The base redirect URI. Must not be {@code null}.
111             * @param code        The authorisation code, {@code null} if not 
112             *                    requested.
113             * @param accessToken The access token, {@code null} if not requested.
114             * @param state       The state, {@code null} if not requested.
115             */
116            public AuthorizationSuccessResponse(final URL redirectURI,
117                                                final AuthorizationCode code,
118                                                final AccessToken accessToken,
119                                                final State state) {
120            
121                    super(redirectURI, state);
122                    
123                    this.code = code;
124                    
125                    this.accessToken = accessToken;
126            }
127            
128            
129            /**
130             * Gets the implied response type set.
131             *
132             * @return The implied response type set.
133             */
134            public ResponseTypeSet getImpliedResponseTypeSet() {
135            
136                    ResponseTypeSet rts = new ResponseTypeSet();
137                    
138                    if (code != null)
139                            rts.add(ResponseType.CODE);
140                    
141                    if (accessToken != null)
142                            rts.add(ResponseType.TOKEN);
143                            
144                    return rts;
145            }
146            
147            
148            /**
149             * Gets the authorisation code.
150             *
151             * @return The authorisation code, {@code null} if not requested.
152             */
153            public AuthorizationCode getAuthorizationCode() {
154            
155                    return code;
156            }
157            
158            
159            /**
160             * Gets the access token.
161             *
162             * @return The access token, {@code null} if not requested.
163             */
164            public AccessToken getAccessToken() {
165            
166                    return accessToken;
167            }
168    
169    
170            @Override
171            public Map<String,String> toParameters()
172                    throws SerializeException {
173    
174                    Map<String,String> params = new HashMap<String,String>();
175    
176                    if (code != null)
177                            params.put("code", code.getValue());
178    
179                    if (accessToken != null) {
180                            
181                            for (Map.Entry<String,Object> entry: accessToken.toJSONObject().entrySet()) {
182    
183                                    params.put(entry.getKey(), entry.getValue().toString());
184                            }
185                    }
186                            
187                    if (getState() != null)
188                            params.put("state", getState().getValue());
189    
190                    return params;
191            }
192            
193            
194            @Override
195            public URL toURI()
196                    throws SerializeException {
197            
198                    StringBuilder sb = new StringBuilder(getRedirectURI().toString());
199                    
200                    // Fragment or query string?
201                    if (accessToken != null)
202                            sb.append('#');
203                    else
204                            sb.append('?');
205                    
206                    
207                    sb.append(URLUtils.serializeParameters(toParameters()));
208    
209    
210                    try {
211                            return new URL(sb.toString());
212                            
213                    } catch (MalformedURLException e) {
214                    
215                            throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e);
216                    }
217            }
218    
219    
220            /**
221             * Parses an authorisation success response.
222             *
223             * @param redirectURI The base redirect URI. Must not be {@code null}.
224             * @param params      The response parameters to parse. Must not be 
225             *                    {@code null}.
226             *
227             * @return The authorisation success response.
228             *
229             * @throws ParseException If the parameters couldn't be parsed to an
230             *                        authorisation success response.
231             */
232            public static AuthorizationSuccessResponse parse(final URL redirectURI, 
233                                                             final Map<String,String> params)
234                    throws ParseException {
235            
236                    // Parse code parameter
237                    
238                    AuthorizationCode code = null;
239                    
240                    if (params.get("code") != null)
241                            code = new AuthorizationCode(params.get("code"));
242                    
243                    
244                    // Parse access_token parameters
245                    
246                    AccessToken accessToken = null;
247                    
248                    if (params.get("access_token") != null) {
249    
250                            JSONObject jsonObject = new JSONObject();
251    
252                            jsonObject.putAll(params);
253    
254                            accessToken = AccessToken.parse(jsonObject);
255                    }
256                    
257                    
258                    // Parse optional state parameter
259                    State state = State.parse(params.get("state"));
260    
261                    
262                    return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state);
263            }
264            
265            
266            /**
267             * Parses an authorisation success response.
268             *
269             * <p>Example URI:
270             *
271             * <pre>
272             * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
273             * </pre>
274             *
275             * @param uri The URI to parse. Can be absolute or relative, with a
276             *            fragment or query string containing the authorisation
277             *            response parameters. Must not be {@code null}.
278             *
279             * @return The authorisation success response.
280             *
281             * @throws ParseException If the redirect URI couldn't be parsed to an
282             *                        authorisation success response.
283             */
284            public static AuthorizationSuccessResponse parse(final URL uri)
285                    throws ParseException {
286                    
287                    String paramString = null;
288                    
289                    if (uri.getQuery() != null)
290                            paramString = uri.getQuery();
291                                    
292                    else if (uri.getRef() != null)
293                            paramString = uri.getRef();
294                    
295                    else
296                            throw new ParseException("Missing authorization response parameters");
297                    
298                    Map<String,String> params = URLUtils.parseParameters(paramString);
299    
300                    if (params == null)
301                            throw new ParseException("Missing or invalid authorization response parameters");
302    
303                    return parse(URLUtils.getBaseURL(uri), params);
304            }
305    
306    
307            /**
308             * Parses an authorisation success response.
309             *
310             * <p>Example HTTP response:
311             *
312             * <pre>
313             * HTTP/1.1 302 Found
314             * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
315             * </pre>
316             *
317             * @param httpResponse The HTTP response to parse. Must not be 
318             *                     {@code null}.
319             *
320             * @return The authorisation success response.
321             *
322             * @throws ParseException If the HTTP response couldn't be parsed to an 
323             *                        authorisation success response.
324             */
325            public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
326                    throws ParseException {
327                    
328                    if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
329                            throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
330                                                     httpResponse.getStatusCode());
331                    
332                    URL location = httpResponse.getLocation();
333                    
334                    if (location == null)
335                            throw new ParseException("Missing redirect URL / HTTP Location header");
336                    
337                    return parse(location);
338            }
339    }