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