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.
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@Immutable
053public class TokenErrorResponse 
054        extends TokenResponse
055        implements ErrorResponse {
056
057
058        /**
059         * The standard OAuth 2.0 errors for an Access Token error response.
060         */
061        private static final Set<ErrorObject> stdErrors = new HashSet<ErrorObject>();
062        
063        
064        static {
065                stdErrors.add(OAuth2Error.INVALID_REQUEST);
066                stdErrors.add(OAuth2Error.INVALID_CLIENT);
067                stdErrors.add(OAuth2Error.INVALID_GRANT);
068                stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
069                stdErrors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE);
070                stdErrors.add(OAuth2Error.INVALID_SCOPE);
071        }
072        
073        
074        /**
075         * Gets the standard OAuth 2.0 errors for an Access Token error 
076         * response.
077         *
078         * @return The standard errors, as a read-only set.
079         */
080        public static Set<ErrorObject> getStandardErrors() {
081        
082                return Collections.unmodifiableSet(stdErrors);
083        }
084        
085        
086        /**
087         * The error.
088         */
089        private final ErrorObject error;
090
091
092        /**
093         * Creates a new OAuth 2.0 Access Token error response. No OAuth 2.0 
094         * error is specified.
095         */
096        protected TokenErrorResponse() {
097
098                error = null;
099        }
100        
101        
102        /**
103         * Creates a new OAuth 2.0 Access Token error response.
104         *
105         * @param error The error. Should match one of the 
106         *              {@link #getStandardErrors standard errors} for a token 
107         *              error response. Must not be {@code null}.
108         */
109        public TokenErrorResponse(final ErrorObject error) {
110        
111                if (error == null)
112                        throw new IllegalArgumentException("The error must not be null");
113                        
114                this.error = error;
115        }
116        
117
118        @Override
119        public ErrorObject getErrorObject() {
120        
121                return error;
122        }
123        
124        
125        /**
126         * Returns the JSON object for this token error response.
127         *
128         * @return The JSON object for this token error response.
129         */
130        public JSONObject toJSONObject() {
131        
132                JSONObject o = new JSONObject();
133
134                // No error?
135                if (error == null)
136                        return o;
137
138                o.put("error", error.getCode());
139
140                if (error.getDescription() != null)
141                        o.put("error_description", error.getDescription());
142                
143                if (error.getURI() != null)
144                        o.put("error_uri", error.getURI().toString());
145                
146                return o;
147        }
148        
149        
150        @Override
151        public HTTPResponse toHTTPResponse() {
152
153                int statusCode = error.getHTTPStatusCode() > 0 ? error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST;
154
155                HTTPResponse httpResponse = new HTTPResponse(statusCode);
156                
157                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
158                httpResponse.setCacheControl("no-store");
159                httpResponse.setPragma("no-cache");
160                
161                httpResponse.setContent(toJSONObject().toString());
162                
163                return httpResponse;
164        }
165
166
167        /**
168         * Parses an OAuth 2.0 Token Error response from the specified JSON
169         * object.
170         *
171         * @param jsonObject The JSON object to parse. Its status code must not
172         *                   be 200 (OK). Must not be {@code null}.
173         *
174         * @throws ParseException If the JSON object couldn't be parsed to an 
175         *                        OAuth 2.0 Token Error response.
176         */
177        public static TokenErrorResponse parse(final JSONObject jsonObject)
178                throws ParseException {
179
180                // No error code?
181                if (! jsonObject.containsKey("error"))
182                        return new TokenErrorResponse();
183                
184                ErrorObject error;
185                
186                try {
187                        // Parse code
188                        String code = JSONObjectUtils.getString(jsonObject, "error");
189
190                        // Parse description
191                        String description = null;
192
193                        if (jsonObject.containsKey("error_description"))
194                                description = JSONObjectUtils.getString(jsonObject, "error_description");
195
196                        // Parse URI
197                        URL uri = null;
198
199                        if (jsonObject.containsKey("error_uri"))
200                                uri = new URL(JSONObjectUtils.getString(jsonObject, "error_uri"));
201
202
203                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
204                        
205                } catch (ParseException e) {
206                
207                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
208                        
209                } catch (MalformedURLException e) {
210                
211                        throw new ParseException("Invalid error URI: " + e.getMessage(), e);
212                }
213                
214                return new TokenErrorResponse(error);
215        }
216        
217        
218        /**
219         * Parses an OAuth 2.0 Token Error response from the specified HTTP
220         * response.
221         *
222         * @param httpResponse The HTTP response to parse. Its status code must
223         *                     not be 200 (OK). Must not be {@code null}.
224         *
225         * @throws ParseException If the HTTP response couldn't be parsed to an 
226         *                        OAuth 2.0 Token Error response.
227         */
228        public static TokenErrorResponse parse(final HTTPResponse httpResponse)
229                throws ParseException {
230                
231                httpResponse.ensureStatusCodeNotOK();
232
233                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
234
235                return parse(jsonObject);
236        }
237}