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