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