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.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.util.List;
025import java.util.Map;
026
027import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
028import com.nimbusds.oauth2.sdk.http.HTTPRequest;
029import com.nimbusds.oauth2.sdk.http.HTTPResponse;
030import com.nimbusds.oauth2.sdk.id.State;
031import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
032import com.nimbusds.oauth2.sdk.util.StringUtils;
033import com.nimbusds.oauth2.sdk.util.URIUtils;
034import com.nimbusds.oauth2.sdk.util.URLUtils;
035
036
037/**
038 * The base abstract class for authorisation success and error responses.
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OAuth 2.0 (RFC 6749), section 3.1.
044 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
045 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
046 * </ul>
047 */
048public abstract class AuthorizationResponse implements Response {
049
050
051        /**
052         * The base redirection URI.
053         */
054        private final URI redirectURI;
055
056
057        /**
058         * The optional state parameter to be echoed back to the client.
059         */
060        private final State state;
061
062
063        /**
064         * The optional explicit response mode.
065         */
066        private final ResponseMode rm;
067
068
069        /**
070         * Creates a new authorisation response.
071         *
072         * @param redirectURI The base redirection URI. Must not be
073         *                    {@code null}.
074         * @param state       The state, {@code null} if not requested.
075         * @param rm          The response mode, {@code null} if not specified.
076         */
077        protected AuthorizationResponse(final URI redirectURI, final State state, final ResponseMode rm) {
078
079                if (redirectURI == null) {
080                        throw new IllegalArgumentException("The redirection URI must not be null");
081                }
082
083                this.redirectURI = redirectURI;
084
085                this.state = state;
086
087                this.rm = rm;
088        }
089
090
091        /**
092         * Returns the base redirection URI.
093         *
094         * @return The base redirection URI (without the appended error
095         *         response parameters).
096         */
097        public URI getRedirectionURI() {
098
099                return redirectURI;
100        }
101
102
103        /**
104         * Returns the optional state.
105         *
106         * @return The state, {@code null} if not requested.
107         */
108        public State getState() {
109
110                return state;
111        }
112
113
114        /**
115         * Returns the optional explicit response mode.
116         *
117         * @return The response mode, {@code null} if not specified.
118         */
119        public ResponseMode getResponseMode() {
120
121                return rm;
122        }
123
124
125        /**
126         * Determines the implied response mode.
127         *
128         * @return The implied response mode.
129         */
130        public abstract ResponseMode impliedResponseMode();
131
132
133        /**
134         * Returns the parameters of this authorisation response.
135         *
136         * <p>Example parameters (authorisation success):
137         *
138         * <pre>
139         * access_token = 2YotnFZFEjr1zCsicMWpAA
140         * state = xyz
141         * token_type = example
142         * expires_in = 3600
143         * </pre>
144         *
145         * @return The parameters as a map.
146         */
147        public abstract Map<String,List<String>> toParameters();
148
149
150        /**
151         * Returns a URI representation (redirection URI + fragment / query
152         * string) of this authorisation response.
153         *
154         * <p>Example URI:
155         *
156         * <pre>
157         * http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
158         * &amp;state=xyz
159         * &amp;token_type=example
160         * &amp;expires_in=3600
161         * </pre>
162         *
163         * @return A URI representation of this authorisation response.
164         */
165        public URI toURI() {
166
167                final ResponseMode rm = impliedResponseMode();
168
169                StringBuilder sb = new StringBuilder(getRedirectionURI().toString());
170
171                if (rm.equals(ResponseMode.QUERY)) {
172                        if (StringUtils.isBlank(getRedirectionURI().getRawQuery())) {
173                                sb.append('?');
174                        } else {
175                                // The original redirect_uri may contain query params,
176                                // see http://tools.ietf.org/html/rfc6749#section-3.1.2
177                                sb.append('&');
178                        }
179                } else if (rm.equals(ResponseMode.FRAGMENT)) {
180                        sb.append('#');
181                } else {
182                        throw new SerializeException("The (implied) response mode must be query or fragment");
183                }
184
185                sb.append(URLUtils.serializeParameters(toParameters()));
186
187                try {
188                        return new URI(sb.toString());
189
190                } catch (URISyntaxException e) {
191
192                        throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e);
193                }
194        }
195
196
197        /**
198         * Returns an HTTP response for this authorisation response. Applies to
199         * the {@code query} or {@code fragment} response mode using HTTP 302
200         * redirection.
201         *
202         * <p>Example HTTP response (authorisation success):
203         *
204         * <pre>
205         * HTTP/1.1 302 Found
206         * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
207         * &amp;state=xyz
208         * &amp;token_type=example
209         * &amp;expires_in=3600
210         * </pre>
211         *
212         * @see #toHTTPRequest()
213         *
214         * @return An HTTP response for this authorisation response.
215         */
216        @Override
217        public HTTPResponse toHTTPResponse() {
218
219                if (ResponseMode.FORM_POST.equals(rm)) {
220                        throw new SerializeException("The response mode must not be form_post");
221                }
222
223                HTTPResponse response= new HTTPResponse(HTTPResponse.SC_FOUND);
224                response.setLocation(toURI());
225                return response;
226        }
227
228
229        /**
230         * Returns an HTTP request for this authorisation response. Applies to
231         * the {@code form_post} response mode.
232         *
233         * <p>Example HTTP request (authorisation success):
234         *
235         * <pre>
236         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
237         * Host: client.example.com
238         * </pre>
239         *
240         * @see #toHTTPResponse()
241         *
242         * @return An HTTP request for this authorisation response.
243         */
244        public HTTPRequest toHTTPRequest() {
245
246                if (! ResponseMode.FORM_POST.equals(rm)) {
247                        throw new SerializeException("The response mode must be form_post");
248                }
249
250                // Use HTTP POST
251                HTTPRequest request;
252
253                try {
254                        request = new HTTPRequest(HTTPRequest.Method.POST, redirectURI.toURL());
255
256                } catch (MalformedURLException e) {
257                        throw new SerializeException(e.getMessage(), e);
258                }
259
260                request.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
261                request.setQuery(URLUtils.serializeParameters(toParameters()));
262                return request;
263        }
264        
265        
266        /**
267         * Casts this response to an authorisation success response.
268         *
269         * @return The authorisation success response.
270         */
271        public AuthorizationSuccessResponse toSuccessResponse() {
272                
273                return (AuthorizationSuccessResponse) this;
274        }
275        
276        
277        /**
278         * Casts this response to an authorisation error response.
279         *
280         * @return The authorisation error response.
281         */
282        public AuthorizationErrorResponse toErrorResponse() {
283                
284                return (AuthorizationErrorResponse) this;
285        }
286
287
288        /**
289         * Parses an authorisation response.
290         *
291         * @param redirectURI The base redirection URI. Must not be
292         *                    {@code null}.
293         * @param params      The response parameters to parse. Must not be
294         *                    {@code null}.
295         *
296         * @return The authorisation success or error response.
297         *
298         * @throws ParseException If the parameters couldn't be parsed to an
299         *                        authorisation success or error response.
300         */
301        public static AuthorizationResponse parse(final URI redirectURI, final Map<String,List<String>> params)
302                throws ParseException {
303
304                if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "error"))) {
305                        return AuthorizationErrorResponse.parse(redirectURI, params);
306                } else {
307                        return AuthorizationSuccessResponse.parse(redirectURI, params);
308                }
309        }
310
311
312        /**
313         * Parses an authorisation 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         * @param uri The URI to parse. Can be absolute or relative, with a
323         *            fragment or query string containing the authorisation
324         *            response parameters. Must not be {@code null}.
325         *
326         * @return The authorisation success or error response.
327         *
328         * @throws ParseException If no authorisation response parameters were
329         *                        found in the URL.
330         */
331        public static AuthorizationResponse parse(final URI uri)
332                throws ParseException {
333
334                Map<String,List<String>> params;
335
336                if (uri.getRawFragment() != null) {
337                        params = URLUtils.parseParameters(uri.getRawFragment());
338                } else if (uri.getRawQuery() != null) {
339                        params = URLUtils.parseParameters(uri.getRawQuery());
340                } else {
341                        throw new ParseException("Missing URI fragment or query string");
342                }
343
344                return parse(URIUtils.getBaseURI(uri), params);
345        }
346
347
348        /**
349         * Parses an authorisation response from the specified initial HTTP 302
350         * redirect response output at the authorisation endpoint.
351         *
352         * <p>Example HTTP response (authorisation success):
353         *
354         * <pre>
355         * HTTP/1.1 302 Found
356         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
357         * </pre>
358         *
359         * @see #parse(HTTPRequest)
360         *
361         * @param httpResponse The HTTP response to parse. Must not be
362         *                     {@code null}.
363         *
364         * @return The authorisation response.
365         *
366         * @throws ParseException If the HTTP response couldn't be parsed to an
367         *                        authorisation response.
368         */
369        public static AuthorizationResponse parse(final HTTPResponse httpResponse)
370                throws ParseException {
371
372                URI location = httpResponse.getLocation();
373
374                if (location == null) {
375                        throw new ParseException("Missing redirection URI / HTTP Location header");
376                }
377
378                return parse(location);
379        }
380
381
382        /**
383         * Parses an authorisation response from the specified HTTP request at
384         * the client redirection (callback) URI. Applies to the {@code query},
385         * {@code fragment} and {@code form_post} response modes.
386         *
387         * <p>Example HTTP request (authorisation success):
388         *
389         * <pre>
390         * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz HTTP/1.1
391         * Host: client.example.com
392         * </pre>
393         *
394         * @see #parse(HTTPResponse)
395         *
396         * @param httpRequest The HTTP request to parse. Must not be
397         *                    {@code null}.
398         *
399         * @return The authorisation response.
400         *
401         * @throws ParseException If the HTTP request couldn't be parsed to an
402         *                        authorisation response.
403         */
404        public static AuthorizationResponse parse(final HTTPRequest httpRequest)
405                throws ParseException {
406
407                final URI baseURI;
408
409                try {
410                        baseURI = httpRequest.getURL().toURI();
411
412                } catch (URISyntaxException e) {
413                        throw new ParseException(e.getMessage(), e);
414                }
415
416                if (httpRequest.getQuery() != null) {
417                        // For query string and form_post response mode
418                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getQuery()));
419                } else if (httpRequest.getFragment() != null) {
420                        // For fragment response mode (never available in actual HTTP request from browser)
421                        return parse(baseURI, URLUtils.parseParameters(httpRequest.getFragment()));
422                } else {
423                        throw new ParseException("Missing URI fragment, query string or post body");
424                }
425        }
426}