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 net.jcip.annotations.Immutable;
011
012import org.apache.commons.lang3.StringUtils;
013
014import net.minidev.json.JSONObject;
015
016import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
017import com.nimbusds.oauth2.sdk.http.HTTPResponse;
018import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
019
020
021/**
022 * OAuth 2.0 Token error response.
023 *
024 * <p>Standard token errors:
025 *
026 * <ul>
027 *     <li>{@link OAuth2Error#INVALID_REQUEST}
028 *     <li>{@link OAuth2Error#INVALID_CLIENT}
029 *     <li>{@link OAuth2Error#INVALID_GRANT}
030 *     <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT}
031 *     <li>{@link OAuth2Error#UNSUPPORTED_GRANT_TYPE}
032 *     <li>{@link OAuth2Error#INVALID_SCOPE}
033 * </ul>
034 *
035 * <p>Example HTTP response:
036 *
037 * <pre>
038 * HTTP/1.1 400 Bad Request
039 * Content-Type: application/json
040 * Cache-Control: no-store
041 * Pragma: no-cache
042 * 
043 * {
044 *  "error": "invalid_request"
045 * }
046 * </pre>
047 *
048 * <p>Related specifications:
049 *
050 * <ul>
051 *     <li>OAuth 2.0 (RFC 6749), section 5.2.
052 * </ul>
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<>();
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                int statusCode = (error != null && error.getHTTPStatusCode() > 0) ?
156                        error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST;
157
158                HTTPResponse httpResponse = new HTTPResponse(statusCode);
159
160                if (error == null)
161                        return httpResponse;
162                
163                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
164                httpResponse.setCacheControl("no-store");
165                httpResponse.setPragma("no-cache");
166                
167                httpResponse.setContent(toJSONObject().toString());
168                
169                return httpResponse;
170        }
171
172
173        /**
174         * Parses an OAuth 2.0 Token Error response from the specified JSON
175         * object.
176         *
177         * @param jsonObject The JSON object to parse. Its status code must not
178         *                   be 200 (OK). Must not be {@code null}.
179         *
180         * @throws ParseException If the JSON object couldn't be parsed to an 
181         *                        OAuth 2.0 Token Error response.
182         */
183        public static TokenErrorResponse parse(final JSONObject jsonObject)
184                throws ParseException {
185
186                // No error code?
187                if (! jsonObject.containsKey("error"))
188                        return new TokenErrorResponse();
189                
190                ErrorObject error;
191                
192                try {
193                        // Parse code
194                        String code = JSONObjectUtils.getString(jsonObject, "error");
195
196                        // Parse description
197                        String description = null;
198
199                        if (jsonObject.containsKey("error_description"))
200                                description = JSONObjectUtils.getString(jsonObject, "error_description");
201
202                        // Parse URI
203                        URI uri = null;
204
205                        if (jsonObject.containsKey("error_uri"))
206                                uri = new URI(JSONObjectUtils.getString(jsonObject, "error_uri"));
207
208
209                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
210                        
211                } catch (ParseException e) {
212                
213                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
214                        
215                } catch (URISyntaxException e) {
216                
217                        throw new ParseException("Invalid error URI: " + e.getMessage(), e);
218                }
219                
220                return new TokenErrorResponse(error);
221        }
222        
223        
224        /**
225         * Parses an OAuth 2.0 Token Error response from the specified HTTP
226         * response.
227         *
228         * @param httpResponse The HTTP response to parse. Its status code must
229         *                     not be 200 (OK). Must not be {@code null}.
230         *
231         * @throws ParseException If the HTTP response couldn't be parsed to an 
232         *                        OAuth 2.0 Token Error response.
233         */
234        public static TokenErrorResponse parse(final HTTPResponse httpResponse)
235                throws ParseException {
236                
237                httpResponse.ensureStatusCodeNotOK();
238
239                if (StringUtils.isNotEmpty(httpResponse.getContent()) &&
240                    CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) {
241
242                        JSONObject jsonObject = httpResponse.getContentAsJSONObject();
243
244                        return parse(jsonObject);
245                }
246
247                return new TokenErrorResponse();
248        }
249}