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 boolean indicatesSuccess() {
122
123                return false;
124        }
125        
126
127        @Override
128        public ErrorObject getErrorObject() {
129        
130                return error;
131        }
132        
133        
134        /**
135         * Returns the JSON object for this token error response.
136         *
137         * @return The JSON object for this token error response.
138         */
139        public JSONObject toJSONObject() {
140        
141                JSONObject o = new JSONObject();
142
143                // No error?
144                if (error == null)
145                        return o;
146
147                o.put("error", error.getCode());
148
149                if (error.getDescription() != null)
150                        o.put("error_description", error.getDescription());
151                
152                if (error.getURI() != null)
153                        o.put("error_uri", error.getURI().toString());
154                
155                return o;
156        }
157        
158        
159        @Override
160        public HTTPResponse toHTTPResponse() {
161
162                int statusCode = (error != null && error.getHTTPStatusCode() > 0) ?
163                        error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST;
164
165                HTTPResponse httpResponse = new HTTPResponse(statusCode);
166
167                if (error == null)
168                        return httpResponse;
169                
170                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
171                httpResponse.setCacheControl("no-store");
172                httpResponse.setPragma("no-cache");
173                
174                httpResponse.setContent(toJSONObject().toString());
175                
176                return httpResponse;
177        }
178
179
180        /**
181         * Parses an OAuth 2.0 Token Error response from the specified JSON
182         * object.
183         *
184         * @param jsonObject The JSON object to parse. Its status code must not
185         *                   be 200 (OK). Must not be {@code null}.
186         *
187         * @throws ParseException If the JSON object couldn't be parsed to an 
188         *                        OAuth 2.0 Token Error response.
189         */
190        public static TokenErrorResponse parse(final JSONObject jsonObject)
191                throws ParseException {
192
193                // No error code?
194                if (! jsonObject.containsKey("error"))
195                        return new TokenErrorResponse();
196                
197                ErrorObject error;
198                
199                try {
200                        // Parse code
201                        String code = JSONObjectUtils.getString(jsonObject, "error");
202
203                        // Parse description
204                        String description = null;
205
206                        if (jsonObject.containsKey("error_description"))
207                                description = JSONObjectUtils.getString(jsonObject, "error_description");
208
209                        // Parse URI
210                        URI uri = null;
211
212                        if (jsonObject.containsKey("error_uri"))
213                                uri = new URI(JSONObjectUtils.getString(jsonObject, "error_uri"));
214
215
216                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
217                        
218                } catch (ParseException e) {
219                
220                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
221                        
222                } catch (URISyntaxException e) {
223                
224                        throw new ParseException("Invalid error URI: " + e.getMessage(), e);
225                }
226                
227                return new TokenErrorResponse(error);
228        }
229        
230        
231        /**
232         * Parses an OAuth 2.0 Token Error response from the specified HTTP
233         * response.
234         *
235         * @param httpResponse The HTTP response to parse. Its status code must
236         *                     not be 200 (OK). Must not be {@code null}.
237         *
238         * @throws ParseException If the HTTP response couldn't be parsed to an 
239         *                        OAuth 2.0 Token Error response.
240         */
241        public static TokenErrorResponse parse(final HTTPResponse httpResponse)
242                throws ParseException {
243                
244                httpResponse.ensureStatusCodeNotOK();
245
246                if (StringUtils.isNotEmpty(httpResponse.getContent()) &&
247                    CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) {
248
249                        JSONObject jsonObject = httpResponse.getContentAsJSONObject();
250
251                        return parse(jsonObject);
252                }
253
254                return new TokenErrorResponse();
255        }
256}