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