001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.URI;
005import java.net.URISyntaxException;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.Map;
010import java.util.Set;
011
012import net.jcip.annotations.Immutable;
013
014import org.apache.commons.lang3.StringUtils;
015
016import com.nimbusds.oauth2.sdk.id.State;
017import com.nimbusds.oauth2.sdk.http.HTTPResponse;
018import com.nimbusds.oauth2.sdk.util.URIUtils;
019import com.nimbusds.oauth2.sdk.util.URLUtils;
020
021
022/**
023 * Authorisation error response.
024 *
025 * <p>Standard authorisation errors:
026 *
027 * <ul>
028 *     <li>{@link OAuth2Error#INVALID_REQUEST}
029 *     <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT}
030 *     <li>{@link OAuth2Error#ACCESS_DENIED}
031 *     <li>{@link OAuth2Error#UNSUPPORTED_RESPONSE_TYPE}
032 *     <li>{@link OAuth2Error#INVALID_SCOPE}
033 *     <li>{@link OAuth2Error#SERVER_ERROR}
034 *     <li>{@link OAuth2Error#TEMPORARILY_UNAVAILABLE}
035 * </ul>
036 *
037 * <p>Example HTTP response:
038 *
039 * <pre>
040 * HTTP/1.1 302 Found
041 * Location: https://client.example.com/cb?
042 * error=invalid_request
043 * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
044 * &amp;state=af0ifjsldkj
045 * </pre>
046 *
047 * <p>Related specifications:
048 *
049 * <ul>
050 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1.
051 * </ul>
052 */
053@Immutable
054public class AuthorizationErrorResponse
055        extends AuthorizationResponse
056        implements ErrorResponse {
057
058
059        /**
060         * The standard OAuth 2.0 errors for an Authorisation error response.
061         */
062        private static final Set<ErrorObject> stdErrors = new HashSet<>();
063        
064        
065        static {
066                stdErrors.add(OAuth2Error.INVALID_REQUEST);
067                stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
068                stdErrors.add(OAuth2Error.ACCESS_DENIED);
069                stdErrors.add(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE);
070                stdErrors.add(OAuth2Error.INVALID_SCOPE);
071                stdErrors.add(OAuth2Error.SERVER_ERROR);
072                stdErrors.add(OAuth2Error.TEMPORARILY_UNAVAILABLE);
073        }
074        
075        
076        /**
077         * Gets the standard OAuth 2.0 errors for an Authorisation error 
078         * response.
079         *
080         * @return The standard errors, as a read-only set.
081         */
082        public static Set<ErrorObject> getStandardErrors() {
083        
084                return Collections.unmodifiableSet(stdErrors);
085        }
086        
087        
088        /**
089         * The error.
090         */
091        private final ErrorObject error;
092        
093        
094        /**
095         * The response type, used to determine redirection URI composition. If
096         * unknown {@code null}.
097         */
098        private final ResponseType rt;
099
100
101        /**
102         * Creates a new authorisation error response.
103         *
104         * @param redirectURI The base redirection URI. Must not be
105         *                    {@code null}.
106         * @param error       The error. Should match one of the
107         *                    {@link #getStandardErrors standard errors} for an
108         *                    authorisation error response. Must not be
109         *                    {@code null}.
110         * @param rt          The response type, used to determine the
111         *                    redirection URI composition. If unknown
112         *                    {@code null}.
113         * @param state       The state, {@code null} if not requested.
114         */
115        public AuthorizationErrorResponse(final URI redirectURI,
116                                          final ErrorObject error,
117                                          final ResponseType rt,
118                                          final State state) {
119
120                super(redirectURI, state);
121
122                if (error == null)
123                        throw new IllegalArgumentException("The error must not be null");
124
125                this.error = error;
126
127                this.rt = rt;
128        }
129        
130        
131        /**
132         * Creates a new authorisation error response, with no specified
133         * response type to determine the redirection URI composition.
134         *
135         * @param redirectURI The base redirection URI. Must not be
136         *                    {@code null}.
137         * @param error       The error. Should match one of the 
138         *                    {@link #getStandardErrors standard errors} for an 
139         *                    authorisation error response. Must not be 
140         *                    {@code null}.
141         * @param state       The state, {@code null} if not requested.
142         */
143        public AuthorizationErrorResponse(final URI redirectURI,
144                                          final ErrorObject error,
145                                          final State state) {
146                                          
147                this(redirectURI, error, null, state);
148        }
149        
150
151        @Override
152        public ErrorObject getErrorObject() {
153        
154                return error;
155        }
156        
157        
158        /**
159         * Gets the response type.
160         *
161         * @return The response type, {@code null} if not specified.
162         */
163        public ResponseType getResponseType() {
164        
165                return rt;
166        }
167
168
169        @Override
170        public Map<String,String> toParameters() {
171
172                Map<String,String> params = new HashMap<>();
173
174                params.put("error", error.getCode());
175
176                if (error.getDescription() != null)
177                        params.put("error_description", error.getDescription());
178
179                if (error.getURI() != null)
180                        params.put("error_uri", error.getURI().toString());
181
182                if (getState() != null)
183                        params.put("state", getState().getValue());
184
185                return params;
186        }
187        
188        
189        @Override
190        public URI toURI()
191                throws SerializeException {
192                
193                StringBuilder sb = new StringBuilder(getRedirectionURI().toString());
194                
195                if (rt == null || rt.contains(ResponseType.Value.TOKEN)) {
196                        sb.append("#");
197                } else {
198                        sb.append("?");
199                }
200
201                sb.append(URLUtils.serializeParameters(toParameters()));
202                
203                try {
204                        return new URI(sb.toString());
205                        
206                } catch (URISyntaxException e) {
207                
208                        throw new SerializeException("Couldn't serialize redirection URI: " + e.getMessage(), e);
209                }
210        }
211
212
213        /**
214         * Parses an authorisation error response from the specified redirect
215         * URI and parameters.
216         *
217         * @param redirectURI The base redirection URI. Must not be
218         *                    {@code null}.
219         * @param params      The response parameters to parse. Must not be 
220         *                    {@code null}.
221         *
222         * @return The authorisation error response.
223         *
224         * @throws ParseException If the parameters couldn't be parsed to an
225         *                        authorisation error response.
226         */
227        public static AuthorizationErrorResponse parse(final URI redirectURI,
228                                                       final Map<String,String> params)
229                throws ParseException {
230
231                // Parse the error
232                if (StringUtils.isBlank(params.get("error")))
233                        throw new ParseException("Missing error code");
234
235                // Parse error code
236                String errorCode = params.get("error");
237
238                String errorDescription = params.get("error_description");
239
240                String errorURIString = params.get("error_uri");
241
242                URI errorURI = null;
243
244                if (errorURIString != null) {
245                        
246                        try {
247                                errorURI = new URI(errorURIString);
248                                
249                        } catch (URISyntaxException e) {
250                
251                                throw new ParseException("Invalid error URI: " + errorURIString, e);
252                        }
253                }
254
255
256                ErrorObject error = new ErrorObject(errorCode, errorDescription, HTTPResponse.SC_FOUND, errorURI);
257                
258                
259                // State
260                State state = State.parse(params.get("state"));
261                
262                return new AuthorizationErrorResponse(redirectURI, error, null, state);
263        }
264        
265        
266        /**
267         * Parses an authorisation error response from the specified URI.
268         *
269         * <p>Example URI:
270         *
271         * <pre>
272         * https://client.example.com/cb?
273         * error=invalid_request
274         * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
275         * &amp;state=af0ifjsldkj
276         * </pre>
277         *
278         * @param uri The URI to parse. Can be absolute or relative. Must not 
279         *            be {@code null}.
280         *
281         * @return The authorisation error response.
282         *
283         * @throws ParseException If the URI couldn't be parsed to an
284         *                        authorisation error response.
285         */
286        public static AuthorizationErrorResponse parse(final URI uri)
287                throws ParseException {
288                
289                Map<String,String> params;
290                
291                if (uri.getRawFragment() != null)
292                        params = URLUtils.parseParameters(uri.getRawFragment());
293
294                else if (uri.getRawQuery() != null)
295                        params = URLUtils.parseParameters(uri.getRawQuery());
296
297                else
298                        throw new ParseException("Missing URI fragment or query string");
299
300                
301                return parse(URIUtils.getBaseURI(uri), params);
302        }
303        
304        
305        /**
306         * Parses an authorisation error response from the specified HTTP
307         * response.
308         *
309         * <p>Example HTTP response:
310         *
311         * <pre>
312         * HTTP/1.1 302 Found
313         * Location: https://client.example.com/cb?
314         * error=invalid_request
315         * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
316         * &amp;state=af0ifjsldkj
317         * </pre>
318         *
319         * @param httpResponse The HTTP response to parse. Must not be 
320         *                     {@code null}.
321         *
322         * @return The authorisation error response.
323         *
324         * @throws ParseException If the HTTP response couldn't be parsed to an 
325         *                        authorisation error response.
326         */
327        public static AuthorizationErrorResponse parse(final HTTPResponse httpResponse)
328                throws ParseException {
329                
330                if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
331                        throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 
332                                                 httpResponse.getStatusCode());
333                
334                URI location = httpResponse.getLocation();
335                
336                if (location == null)
337                        throw new ParseException("Missing redirection URI / HTTP Location header");
338
339                return parse(location);
340        }
341}