001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.Set;
026
027import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
028import com.nimbusds.oauth2.sdk.http.HTTPResponse;
029import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
030import net.jcip.annotations.Immutable;
031import net.minidev.json.JSONObject;
032
033
034/**
035 * OAuth 2.0 Token error response.
036 *
037 * <p>Standard token errors:
038 *
039 * <ul>
040 *     <li>{@link OAuth2Error#INVALID_REQUEST}
041 *     <li>{@link OAuth2Error#INVALID_CLIENT}
042 *     <li>{@link OAuth2Error#INVALID_GRANT}
043 *     <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT}
044 *     <li>{@link OAuth2Error#UNSUPPORTED_GRANT_TYPE}
045 *     <li>{@link OAuth2Error#INVALID_SCOPE}
046 * </ul>
047 *
048 * <p>Example HTTP response:
049 *
050 * <pre>
051 * HTTP/1.1 400 Bad Request
052 * Content-Type: application/json
053 * Cache-Control: no-store
054 * Pragma: no-cache
055 * 
056 * {
057 *  "error": "invalid_request"
058 * }
059 * </pre>
060 *
061 * <p>Related specifications:
062 *
063 * <ul>
064 *     <li>OAuth 2.0 (RFC 6749), section 5.2.
065 * </ul>
066 */
067@Immutable
068public class TokenErrorResponse extends TokenResponse implements ErrorResponse {
069
070
071        /**
072         * The standard OAuth 2.0 errors for an Access Token error response.
073         */
074        private static final Set<ErrorObject> STANDARD_ERRORS;
075        
076        
077        static {
078                Set<ErrorObject> errors = new HashSet<>();
079                errors.add(OAuth2Error.INVALID_REQUEST);
080                errors.add(OAuth2Error.INVALID_CLIENT);
081                errors.add(OAuth2Error.INVALID_GRANT);
082                errors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
083                errors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE);
084                errors.add(OAuth2Error.INVALID_SCOPE);
085                STANDARD_ERRORS = Collections.unmodifiableSet(errors);
086        }
087        
088        
089        /**
090         * Gets the standard OAuth 2.0 errors for an Access Token error 
091         * response.
092         *
093         * @return The standard errors, as a read-only set.
094         */
095        public static Set<ErrorObject> getStandardErrors() {
096        
097                return STANDARD_ERRORS;
098        }
099        
100        
101        /**
102         * The error.
103         */
104        private final ErrorObject error;
105
106
107        /**
108         * Creates a new OAuth 2.0 Access Token error response. No OAuth 2.0
109         * error is specified.
110         */
111        protected TokenErrorResponse() {
112
113                error = null;
114        }
115        
116        
117        /**
118         * Creates a new OAuth 2.0 Access Token error response.
119         *
120         * @param error The error. Should match one of the 
121         *              {@link #getStandardErrors standard errors} for a token 
122         *              error response. Must not be {@code null}.
123         */
124        public TokenErrorResponse(final ErrorObject error) {
125        
126                if (error == null)
127                        throw new IllegalArgumentException("The error must not be null");
128                        
129                this.error = error;
130        }
131
132
133        @Override
134        public boolean indicatesSuccess() {
135
136                return false;
137        }
138        
139
140        @Override
141        public ErrorObject getErrorObject() {
142        
143                return error;
144        }
145        
146        
147        /**
148         * Returns the JSON object for this token error response.
149         *
150         * @return The JSON object for this token error response.
151         */
152        public JSONObject toJSONObject() {
153        
154                JSONObject o = new JSONObject();
155
156                // No error?
157                if (error == null)
158                        return o;
159
160                o.put("error", error.getCode());
161
162                if (error.getDescription() != null)
163                        o.put("error_description", error.getDescription());
164                
165                if (error.getURI() != null)
166                        o.put("error_uri", error.getURI().toString());
167                
168                return o;
169        }
170        
171        
172        @Override
173        public HTTPResponse toHTTPResponse() {
174
175                int statusCode = (error != null && error.getHTTPStatusCode() > 0) ?
176                        error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST;
177
178                HTTPResponse httpResponse = new HTTPResponse(statusCode);
179
180                if (error == null)
181                        return httpResponse;
182                
183                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
184                httpResponse.setCacheControl("no-store");
185                httpResponse.setPragma("no-cache");
186                
187                httpResponse.setContent(toJSONObject().toString());
188                
189                return httpResponse;
190        }
191
192
193        /**
194         * Parses an OAuth 2.0 Token Error response from the specified JSON
195         * object.
196         *
197         * @param jsonObject The JSON object to parse. Its status code must not
198         *                   be 200 (OK). Must not be {@code null}.
199         *
200         * @throws ParseException If the JSON object couldn't be parsed to an 
201         *                        OAuth 2.0 Token Error response.
202         */
203        public static TokenErrorResponse parse(final JSONObject jsonObject)
204                throws ParseException {
205
206                // No error code?
207                if (! jsonObject.containsKey("error"))
208                        return new TokenErrorResponse();
209                
210                ErrorObject error;
211                
212                try {
213                        // Parse code
214                        String code = JSONObjectUtils.getString(jsonObject, "error");
215
216                        // Parse description
217                        String description = null;
218
219                        if (jsonObject.containsKey("error_description"))
220                                description = JSONObjectUtils.getString(jsonObject, "error_description");
221
222                        // Parse URI
223                        URI uri = null;
224
225                        if (jsonObject.containsKey("error_uri"))
226                                uri = new URI(JSONObjectUtils.getString(jsonObject, "error_uri"));
227
228
229                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
230                        
231                } catch (ParseException e) {
232                
233                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e);
234                        
235                } catch (URISyntaxException e) {
236                
237                        throw new ParseException("Invalid error URI: " + e.getMessage(), e);
238                }
239                
240                return new TokenErrorResponse(error);
241        }
242        
243        
244        /**
245         * Parses an OAuth 2.0 Token Error response from the specified HTTP
246         * response.
247         *
248         * @param httpResponse The HTTP response to parse. Its status code must
249         *                     not be 200 (OK). Must not be {@code null}.
250         *
251         * @throws ParseException If the HTTP response couldn't be parsed to an 
252         *                        OAuth 2.0 Token Error response.
253         */
254        public static TokenErrorResponse parse(final HTTPResponse httpResponse)
255                throws ParseException {
256                
257                httpResponse.ensureStatusCodeNotOK();
258                return new TokenErrorResponse(ErrorObject.parse(httpResponse));
259        }
260}