001    package com.nimbusds.oauth2.sdk;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    
007    import java.util.Collections;
008    import java.util.HashSet;
009    import java.util.Set;
010    
011    import net.jcip.annotations.Immutable;
012    
013    import net.minidev.json.JSONObject;
014    
015    import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
016    import com.nimbusds.oauth2.sdk.http.HTTPResponse;
017    
018    import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
019    
020    
021    /**
022     * OAuth 2.0 Token error response. This class is immutable.
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     * @author Vladimir Dzhuvinov
055     * @version $version$ (2013-02-25)
056     */
057    @Immutable
058    public class TokenErrorResponse 
059            extends TokenResponse
060            implements ErrorResponse {
061    
062    
063            /**
064             * The standard OAuth 2.0 errors for an Access Token error response.
065             */
066            private static final Set<ErrorObject> stdErrors = new HashSet<ErrorObject>();
067            
068            
069            static {
070                    stdErrors.add(OAuth2Error.INVALID_REQUEST);
071                    stdErrors.add(OAuth2Error.INVALID_CLIENT);
072                    stdErrors.add(OAuth2Error.INVALID_GRANT);
073                    stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
074                    stdErrors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE);
075                    stdErrors.add(OAuth2Error.INVALID_SCOPE);
076            }
077            
078            
079            /**
080             * Gets the standard OAuth 2.0 errors for an Access Token error 
081             * response.
082             *
083             * @return The standard errors, as a read-only set.
084             */
085            public static Set<ErrorObject> getStandardErrors() {
086            
087                    return Collections.unmodifiableSet(stdErrors);
088            }
089            
090            
091            /**
092             * The error.
093             */
094            private final ErrorObject error;
095    
096    
097            /**
098             * Creates a new OAuth 2.0 Access Token error response. No OAuth 2.0 
099             * error is specified.
100             */
101            protected TokenErrorResponse() {
102    
103                    error = null;
104            }
105            
106            
107            /**
108             * Creates a new OAuth 2.0 Access Token error response.
109             *
110             * @param error The error. Should match one of the 
111             *              {@link #getStandardErrors standard errors} for a token 
112             *              error response. Must not be {@code null}.
113             */
114            public TokenErrorResponse(final ErrorObject error) {
115            
116                    if (error == null)
117                            throw new IllegalArgumentException("The error must not be null");
118                            
119                    this.error = error;
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                    // HTTP status 400
159                    HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
160                    
161                    httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
162                    httpResponse.setCacheControl("no-store");
163                    httpResponse.setPragma("no-cache");
164                    
165                    httpResponse.setContent(toJSONObject().toString());
166                    
167                    return httpResponse;
168            }
169    
170    
171            /**
172             * Parses an OAuth 2.0 Token Error response from the specified JSON
173             * object.
174             *
175             * @param jsonObject The JSON object to parse. Its status code must not
176             *                   be 200 (OK). Must not be {@code null}.
177             *
178             * @throws ParseException If the JSON object couldn't be parsed to an 
179             *                        OAuth 2.0 Token Error response.
180             */
181            public static TokenErrorResponse parse(final JSONObject jsonObject)
182                    throws ParseException {
183    
184                    // No error code?
185                    if (! jsonObject.containsKey("error"))
186                            return new TokenErrorResponse();
187                    
188                    ErrorObject error = null;
189                    
190                    try {
191                            // Parse code
192                            String code = JSONObjectUtils.getString(jsonObject, "error");
193    
194                            // Parse description
195                            String description = null;
196    
197                            if (jsonObject.containsKey("error_description"))
198                                    description = JSONObjectUtils.getString(jsonObject, "error_description");
199    
200                            // Parse URI
201                            URL uri = null;
202    
203                            if (jsonObject.containsKey("error_uri"))
204                                    uri = new URL(JSONObjectUtils.getString(jsonObject, "error_uri"));
205    
206    
207                            error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
208                            
209                    } catch (ParseException e) {
210                    
211                            throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
212                            
213                    } catch (MalformedURLException e) {
214                    
215                            throw new ParseException("Invalid error URI: " + e.getMessage(), e);
216                    }
217                    
218                    return new TokenErrorResponse(error);
219            }
220            
221            
222            /**
223             * Parses an OAuth 2.0 Token Error response from the specified HTTP
224             * response.
225             *
226             * @param httpResponse The HTTP response to parse. Its status code must
227             *                     not be 200 (OK). Must not be {@code null}.
228             *
229             * @throws ParseException If the HTTP response couldn't be parsed to an 
230             *                        OAuth 2.0 Token Error response.
231             */
232            public static TokenErrorResponse parse(final HTTPResponse httpResponse)
233                    throws ParseException {
234                    
235                    httpResponse.ensureStatusCodeNotOK();
236    
237                    JSONObject jsonObject = httpResponse.getContentAsJSONObject();
238    
239                    return parse(jsonObject);
240            }
241    }