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