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