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