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