001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.MalformedURLException;
005import java.net.URI;
006import java.net.URISyntaxException;
007import java.net.URL;
008import java.util.Map;
009
010import com.nimbusds.oauth2.sdk.util.URIUtils;
011import org.apache.commons.lang3.StringUtils;
012
013import com.nimbusds.oauth2.sdk.id.State;
014import com.nimbusds.oauth2.sdk.http.HTTPResponse;
015import com.nimbusds.oauth2.sdk.util.URLUtils;
016
017
018/**
019 * The base abstract class for authorisation success and error responses.
020 *
021 * <p>Related specifications:
022 *
023 * <ul>
024 *     <li>OAuth 2.0 (RFC 6749), section 3.1.
025 * </ul>
026 */
027public abstract class AuthorizationResponse implements Response {
028
029
030        /**
031         * The base redirection URI.
032         */
033        private final URI redirectURI;
034
035
036        /**
037         * The optional state parameter to be echoed back to the client.
038         */
039        private final State state;
040
041
042        /**
043         * Creates a new authorisation response.
044         *
045         * @param redirectURI The base redirection URI. Must not be
046         *                    {@code null}.
047         * @param state       The state, {@code null} if not requested.
048         */
049        protected AuthorizationResponse(final URI redirectURI, final State state) {
050
051                if (redirectURI == null)
052                        throw new IllegalArgumentException("The redirection URI must not be null");
053                
054                this.redirectURI = redirectURI;
055
056                this.state = state;
057        }
058
059
060        /**
061         * Gets the base redirection URI.
062         *
063         * @return The base redirection URI (without the appended error
064         *         response parameters).
065         */
066        public URI getRedirectionURI() {
067        
068                return redirectURI;
069        }
070
071
072        /**
073         * Gets the optional state.
074         *
075         * @return The state, {@code null} if not requested.
076         */
077        public State getState() {
078        
079                return state;
080        }
081
082
083        /**
084         * Returns the parameters of this authorisation response.
085         *
086         * <p>Example parameters (authorisation success):
087         *
088         * <pre>
089         * access_token = 2YotnFZFEjr1zCsicMWpAA
090         * state = xyz
091         * token_type = example
092         * expires_in = 3600
093         * </pre>
094         *
095         * @return The parameters as a map.
096         *
097         * @throws SerializeException If this response couldn't be serialised 
098         *                            to a parameters map.
099         */
100        public abstract Map<String,String> toParameters()
101                throws SerializeException;
102
103
104        /**
105         * Returns the URI representation (redirection URI + fragment / query
106         * string) of this authorisation response.
107         *
108         * <p>Example URI:
109         *
110         * <pre>
111         * http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
112         * &amp;state=xyz
113         * &amp;token_type=example
114         * &amp;expires_in=3600
115         * </pre>
116         *
117         * @return The URI representation of this authorisation response.
118         *
119         * @throws SerializeException If this response couldn't be serialised 
120         *                            to a URI.
121         */
122        public abstract URI toURI()
123                throws SerializeException;
124
125
126        /**
127         * Returns the HTTP response for this authorisation response.
128         *
129         * <p>Example HTTP response (authorisation success):
130         *
131         * <pre>
132         * HTTP/1.1 302 Found
133         * Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
134         * &amp;state=xyz
135         * &amp;token_type=example
136         * &amp;expires_in=3600
137         * </pre>
138         *
139         * @return The HTTP response matching this authorisation response.
140         *
141         * @throws SerializeException If the response couldn't be serialised to
142         *                            an HTTP response.
143         */
144        @Override
145        public HTTPResponse toHTTPResponse()
146                throws SerializeException {
147        
148                HTTPResponse response = new HTTPResponse(HTTPResponse.SC_FOUND);
149
150                try {
151                        response.setLocation(toURI().toURL());
152
153                } catch (MalformedURLException e) {
154
155                        throw new SerializeException(e.getMessage(), e);
156                }
157                
158                return response;
159        }
160
161
162        /**
163         * Parses an authorisation response.
164         *
165         * @param redirectURI The base redirection URI. Must not be
166         *                    {@code null}.
167         * @param params      The response parameters to parse. Must not be 
168         *                    {@code null}.
169         *
170         * @return The authorisation success or error response.
171         *
172         * @throws ParseException If the parameters couldn't be parsed to an
173         *                        authorisation success or error response.
174         */
175        public static AuthorizationResponse parse(final URI redirectURI, final Map<String,String> params)
176                throws ParseException {
177
178                if (StringUtils.isNotBlank(params.get("error")))
179                        return AuthorizationErrorResponse.parse(redirectURI, params);
180
181                else
182                        return AuthorizationSuccessResponse.parse(redirectURI, params);
183        }
184
185
186        /**
187         * Parses an authorisation response.
188         *
189         * <p>Use a relative URI if the host, port and path details are not
190         * known:
191         *
192         * <pre>
193         * URI relUrl = new URI("http://?code=Qcb0Orv1...&state=af0ifjsldkj");
194         * AuthorizationResponse = AuthorizationResponse.parse(relURL);
195         * </pre>
196         *
197         * @param uri The URI to parse. May be absolute or relative, with a
198         *            fragment or query string containing the authorisation
199         *            response parameters. Must not be {@code null}.
200         *
201         * @return The authorisation success or error response.
202         *
203         * @throws ParseException If no authorisation response parameters were
204         *                        found in the URL.
205         */
206        public static AuthorizationResponse parse(final URI uri)
207                throws ParseException {
208
209                Map<String,String> params;
210                
211                if (uri.getRawFragment() != null)
212                        params = URLUtils.parseParameters(uri.getRawFragment());
213
214                else if (uri.getQuery() != null)
215                        params = URLUtils.parseParameters(uri.getQuery());
216
217                else
218                        throw new ParseException("Missing URI fragment or query string");
219
220                
221                return parse(URIUtils.getBaseURI(uri), params);
222        }
223
224
225        /**
226         * Parses an authorisation response.
227         *
228         * <p>Example HTTP response (authorisation success):
229         *
230         * <pre>
231         * HTTP/1.1 302 Found
232         * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&amp;state=xyz
233         * </pre>
234         *
235         * @param httpResponse The HTTP response to parse. Must not be 
236         *                     {@code null}.
237         *
238         * @throws ParseException If the HTTP response couldn't be parsed to an 
239         *                        authorisation response.
240         */
241        public static AuthorizationResponse parse(final HTTPResponse httpResponse)
242                throws ParseException {
243                
244                if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
245                        throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
246                                                 httpResponse.getStatusCode());
247                
248                URL location = httpResponse.getLocation();
249                
250                if (location == null)
251                        throw new ParseException("Missing redirection URI / HTTP Location header");
252
253                try {
254                        return parse(location.toURI());
255
256                } catch (URISyntaxException e) {
257
258                        throw new ParseException(e.getMessage(), e);
259                }
260        }
261}