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