001package com.nimbusds.oauth2.sdk;
002
003
004import java.net.MalformedURLException;
005import java.net.URL;
006import java.util.Collections;
007import java.util.HashSet;
008import java.util.Set;
009
010import net.jcip.annotations.Immutable;
011
012import net.minidev.json.JSONObject;
013
014import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
015import com.nimbusds.oauth2.sdk.http.HTTPResponse;
016import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
017
018
019/**
020 * OAuth 2.0 Token error response. This class is immutable.
021 *
022 * <p>Standard token errors:
023 *
024 * <ul>
025 *     <li>{@link OAuth2Error#INVALID_REQUEST}
026 *     <li>{@link OAuth2Error#INVALID_CLIENT}
027 *     <li>{@link OAuth2Error#INVALID_GRANT}
028 *     <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT}
029 *     <li>{@link OAuth2Error#UNSUPPORTED_GRANT_TYPE}
030 *     <li>{@link OAuth2Error#INVALID_SCOPE}
031 * </ul>
032 *
033 * <p>Example HTTP response:
034 *
035 * <pre>
036 * HTTP/1.1 400 Bad Request
037 * Content-Type: application/json
038 * Cache-Control: no-store
039 * Pragma: no-cache
040 * 
041 * {
042 *  "error": "invalid_request"
043 * }
044 * </pre>
045 *
046 * <p>Related specifications:
047 *
048 * <ul>
049 *     <li>OAuth 2.0 (RFC 6749), section 5.2.
050 * </ul>
051 *
052 * @author Vladimir Dzhuvinov
053 */
054@Immutable
055public class TokenErrorResponse 
056        extends TokenResponse
057        implements ErrorResponse {
058
059
060        /**
061         * The standard OAuth 2.0 errors for an Access Token error response.
062         */
063        private static final Set<ErrorObject> stdErrors = new HashSet<ErrorObject>();
064        
065        
066        static {
067                stdErrors.add(OAuth2Error.INVALID_REQUEST);
068                stdErrors.add(OAuth2Error.INVALID_CLIENT);
069                stdErrors.add(OAuth2Error.INVALID_GRANT);
070                stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
071                stdErrors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE);
072                stdErrors.add(OAuth2Error.INVALID_SCOPE);
073        }
074        
075        
076        /**
077         * Gets the standard OAuth 2.0 errors for an Access Token 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         * Creates a new OAuth 2.0 Access Token error response. No OAuth 2.0 
096         * error is specified.
097         */
098        protected TokenErrorResponse() {
099
100                error = null;
101        }
102        
103        
104        /**
105         * Creates a new OAuth 2.0 Access Token error response.
106         *
107         * @param error The error. Should match one of the 
108         *              {@link #getStandardErrors standard errors} for a token 
109         *              error response. Must not be {@code null}.
110         */
111        public TokenErrorResponse(final ErrorObject error) {
112        
113                if (error == null)
114                        throw new IllegalArgumentException("The error must not be null");
115                        
116                this.error = error;
117        }
118        
119
120        @Override
121        public ErrorObject getErrorObject() {
122        
123                return error;
124        }
125        
126        
127        /**
128         * Returns the JSON object for this token error response.
129         *
130         * @return The JSON object for this token error response.
131         */
132        public JSONObject toJSONObject() {
133        
134                JSONObject o = new JSONObject();
135
136                // No error?
137                if (error == null)
138                        return o;
139
140                o.put("error", error.getCode());
141
142                if (error.getDescription() != null)
143                        o.put("error_description", error.getDescription());
144                
145                if (error.getURI() != null)
146                        o.put("error_uri", error.getURI().toString());
147                
148                return o;
149        }
150        
151        
152        @Override
153        public HTTPResponse toHTTPResponse() {
154                
155                // HTTP status 400
156                HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
157                
158                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
159                httpResponse.setCacheControl("no-store");
160                httpResponse.setPragma("no-cache");
161                
162                httpResponse.setContent(toJSONObject().toString());
163                
164                return httpResponse;
165        }
166
167
168        /**
169         * Parses an OAuth 2.0 Token Error response from the specified JSON
170         * object.
171         *
172         * @param jsonObject The JSON object to parse. Its status code must not
173         *                   be 200 (OK). Must not be {@code null}.
174         *
175         * @throws ParseException If the JSON object couldn't be parsed to an 
176         *                        OAuth 2.0 Token Error response.
177         */
178        public static TokenErrorResponse parse(final JSONObject jsonObject)
179                throws ParseException {
180
181                // No error code?
182                if (! jsonObject.containsKey("error"))
183                        return new TokenErrorResponse();
184                
185                ErrorObject error = null;
186                
187                try {
188                        // Parse code
189                        String code = JSONObjectUtils.getString(jsonObject, "error");
190
191                        // Parse description
192                        String description = null;
193
194                        if (jsonObject.containsKey("error_description"))
195                                description = JSONObjectUtils.getString(jsonObject, "error_description");
196
197                        // Parse URI
198                        URL uri = null;
199
200                        if (jsonObject.containsKey("error_uri"))
201                                uri = new URL(JSONObjectUtils.getString(jsonObject, "error_uri"));
202
203
204                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
205                        
206                } catch (ParseException e) {
207                
208                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
209                        
210                } catch (MalformedURLException e) {
211                
212                        throw new ParseException("Invalid error URI: " + e.getMessage(), e);
213                }
214                
215                return new TokenErrorResponse(error);
216        }
217        
218        
219        /**
220         * Parses an OAuth 2.0 Token Error response from the specified HTTP
221         * response.
222         *
223         * @param httpResponse The HTTP response to parse. Its status code must
224         *                     not be 200 (OK). Must not be {@code null}.
225         *
226         * @throws ParseException If the HTTP response couldn't be parsed to an 
227         *                        OAuth 2.0 Token Error response.
228         */
229        public static TokenErrorResponse parse(final HTTPResponse httpResponse)
230                throws ParseException {
231                
232                httpResponse.ensureStatusCodeNotOK();
233
234                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
235
236                return parse(jsonObject);
237        }
238}