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