001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.HashMap;
024import java.util.Map;
025
026import net.jcip.annotations.Immutable;
027
028import net.minidev.json.JSONObject;
029
030import com.nimbusds.oauth2.sdk.id.State;
031import com.nimbusds.oauth2.sdk.token.AccessToken;
032import com.nimbusds.oauth2.sdk.http.HTTPRequest;
033import com.nimbusds.oauth2.sdk.http.HTTPResponse;
034import com.nimbusds.oauth2.sdk.util.URIUtils;
035import com.nimbusds.oauth2.sdk.util.URLUtils;
036
037
038/**
039 * Authorisation success response. Used to return an authorisation code or 
040 * access token at the Authorisation endpoint.
041 *
042 * <p>Example HTTP response with code (code flow):
043 *
044 * <pre>
045 * HTTP/1.1 302 Found
046 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
047 * </pre>
048 *
049 * <p>Example HTTP response with access token (implicit flow):
050 *
051 * <pre>
052 * HTTP/1.1 302 Found
053 * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
054 *           &amp;state=xyz&amp;token_type=Bearer&amp;expires_in=3600
055 * </pre>
056 *
057 * <p>Related specifications:
058 *
059 * <ul>
060 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.2 and 4.2.2.
061 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
062 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
063 * </ul>
064 */
065@Immutable
066public class AuthorizationSuccessResponse 
067        extends AuthorizationResponse 
068        implements SuccessResponse {
069        
070        
071        /**
072         * The authorisation code, if requested.
073         */
074        private final AuthorizationCode code;
075        
076        
077        /**
078         * The access token, if requested.
079         */
080        private final AccessToken accessToken;
081        
082        
083        /**
084         * Creates a new authorisation success response.
085         *
086         * @param redirectURI The base redirection URI. Must not be
087         *                    {@code null}.
088         * @param code        The authorisation code, {@code null} if not 
089         *                    requested.
090         * @param accessToken The access token, {@code null} if not requested.
091         * @param state       The state, {@code null} if not specified.
092         * @param rm          The response mode, {@code null} if not specified.
093         */
094        public AuthorizationSuccessResponse(final URI redirectURI,
095                                            final AuthorizationCode code,
096                                            final AccessToken accessToken,
097                                            final State state,
098                                            final ResponseMode rm) {
099        
100                super(redirectURI, state, rm);
101                this.code = code;
102                this.accessToken = accessToken;
103        }
104
105
106        @Override
107        public boolean indicatesSuccess() {
108
109                return true;
110        }
111        
112        
113        /**
114         * Returns the implied response type.
115         *
116         * @return The implied response type.
117         */
118        public ResponseType impliedResponseType() {
119        
120                ResponseType rt = new ResponseType();
121                
122                if (code != null)
123                        rt.add(ResponseType.Value.CODE);
124                
125                if (accessToken != null)
126                        rt.add(ResponseType.Value.TOKEN);
127                        
128                return rt;
129        }
130
131
132        @Override
133        public ResponseMode impliedResponseMode() {
134
135                if (getResponseMode() != null) {
136                        return getResponseMode();
137                } else {
138                        if (accessToken != null) {
139                                return ResponseMode.FRAGMENT;
140                        } else {
141                                return ResponseMode.QUERY;
142                        }
143                }
144        }
145        
146        
147        /**
148         * Gets the authorisation code.
149         *
150         * @return The authorisation code, {@code null} if not requested.
151         */
152        public AuthorizationCode getAuthorizationCode() {
153        
154                return code;
155        }
156        
157        
158        /**
159         * Gets the access token.
160         *
161         * @return The access token, {@code null} if not requested.
162         */
163        public AccessToken getAccessToken() {
164        
165                return accessToken;
166        }
167
168
169        @Override
170        public Map<String,String> toParameters() {
171
172                Map<String,String> params = new HashMap<>();
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        /**
193         * Parses an authorisation success response.
194         *
195         * @param redirectURI The base redirection URI. Must not be
196         *                    {@code null}.
197         * @param params      The response parameters to parse. Must not be 
198         *                    {@code null}.
199         *
200         * @return The authorisation success response.
201         *
202         * @throws ParseException If the parameters couldn't be parsed to an
203         *                        authorisation success response.
204         */
205        public static AuthorizationSuccessResponse parse(final URI redirectURI,
206                                                         final Map<String,String> params)
207                throws ParseException {
208        
209                // Parse code parameter
210                
211                AuthorizationCode code = null;
212                
213                if (params.get("code") != null) {
214                        code = new AuthorizationCode(params.get("code"));
215                }
216                
217                // Parse access_token parameters
218                
219                AccessToken accessToken = null;
220                
221                if (params.get("access_token") != null) {
222
223                        JSONObject jsonObject = new JSONObject();
224                        jsonObject.putAll(params);
225                        accessToken = AccessToken.parse(jsonObject);
226                }
227                
228                // Parse optional state parameter
229                State state = State.parse(params.get("state"));
230                
231                return new AuthorizationSuccessResponse(redirectURI, code, accessToken, state, null);
232        }
233        
234        
235        /**
236         * Parses an authorisation success response.
237         *
238         * <p>Use a relative URI if the host, port and path details are not
239         * known:
240         *
241         * <pre>
242         * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj");
243         * </pre>
244         *
245         * <p>Example URI:
246         *
247         * <pre>
248         * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
249         * </pre>
250         *
251         * @param uri The URI to parse. Can be absolute or relative, with a
252         *            fragment or query string containing the authorisation
253         *            response parameters. Must not be {@code null}.
254         *
255         * @return The authorisation success response.
256         *
257         * @throws ParseException If the redirection URI couldn't be parsed to
258         *                        an authorisation success response.
259         */
260        public static AuthorizationSuccessResponse parse(final URI uri)
261                throws ParseException {
262
263                Map<String,String> params;
264
265                if (uri.getRawFragment() != null) {
266
267                        params = URLUtils.parseParameters(uri.getRawFragment());
268
269                } else if (uri.getRawQuery() != null) {
270
271                        params = URLUtils.parseParameters(uri.getRawQuery());
272
273                } else {
274
275                        throw new ParseException("Missing URI fragment or query string");
276                }
277
278                return parse(URIUtils.getBaseURI(uri), params);
279        }
280
281
282        /**
283         * Parses an authorisation success response from the specified initial
284         * HTTP 302 redirect response generated at the authorisation endpoint.
285         *
286         * <p>Example HTTP response:
287         *
288         * <pre>
289         * HTTP/1.1 302 Found
290         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
291         * </pre>
292         *
293         * @see #parse(HTTPRequest)
294         *
295         * @param httpResponse The HTTP response to parse. Must not be 
296         *                     {@code null}.
297         *
298         * @return The authorisation success response.
299         *
300         * @throws ParseException If the HTTP response couldn't be parsed to an 
301         *                        authorisation success response.
302         */
303        public static AuthorizationSuccessResponse parse(final HTTPResponse httpResponse)
304                throws ParseException {
305                
306                URI location = httpResponse.getLocation();
307                
308                if (location == null) {
309                        throw new ParseException("Missing redirection URL / HTTP Location header");
310                }
311
312                return parse(location);
313        }
314
315
316        /**
317         * Parses an authorisation success response from the specified HTTP
318         * request at the client redirection (callback) URI. Applies to
319         * {@code query}, {@code fragment} and {@code form_post} response
320         * modes.
321         *
322         * <p>Example HTTP request (authorisation success):
323         *
324         * <pre>
325         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
326         * Host: client.example.com
327         * </pre>
328         *
329         * @see #parse(HTTPResponse)
330         *
331         * @param httpRequest The HTTP request to parse. Must not be
332         *                    {@code null}.
333         *
334         * @throws ParseException If the HTTP request couldn't be parsed to an
335         *                        authorisation success response.
336         */
337        public static AuthorizationSuccessResponse parse(final HTTPRequest httpRequest)
338                throws ParseException {
339
340                final URI baseURI;
341
342                try {
343                        baseURI = httpRequest.getURL().toURI();
344
345                } catch (URISyntaxException e) {
346                        throw new ParseException(e.getMessage(), e);
347                }
348
349                if (httpRequest.getQuery() != null) {
350                        // For query string and form_post response mode
351                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery()));
352                } else if (httpRequest.getFragment() != null) {
353                        // For fragment response mode (never available in actual HTTP request from browser)
354                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment()));
355                } else {
356                        throw new ParseException("Missing URI fragment, query string or post body");
357                }
358        }
359}