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.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import net.jcip.annotations.Immutable;
029import net.minidev.json.JSONObject;
030
031import com.nimbusds.oauth2.sdk.http.HTTPResponse;
032import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
033import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
034
035
036/**
037 * Error object, used to encapsulate OAuth 2.0 and other errors.
038 *
039 * <p>Example error object as HTTP response:
040 *
041 * <pre>
042 * HTTP/1.1 400 Bad Request
043 * Content-Type: application/json;charset=UTF-8
044 * Cache-Control: no-store
045 * Pragma: no-cache
046 *
047 * {
048 *   "error" : "invalid_request"
049 * }
050 * </pre>
051 */
052@Immutable
053public class ErrorObject {
054        
055        
056        /**
057         * The error code, may not always be defined.
058         */
059        private final String code;
060
061
062        /**
063         * Optional error description.
064         */
065        private final String description;
066
067
068        /**
069         * Optional HTTP status code, 0 if not specified.
070         */
071        private final int httpStatusCode;
072
073
074        /**
075         * Optional URI of a web page that includes additional information 
076         * about the error.
077         */
078        private final URI uri;
079
080
081        /**
082         * Creates a new error with the specified code.
083         *
084         * @param code The error code, {@code null} if not specified.
085         */
086        public ErrorObject(final String code) {
087        
088                this(code, null, 0, null);
089        }
090        
091        
092        /**
093         * Creates a new error with the specified code and description.
094         *
095         * @param code        The error code, {@code null} if not specified.
096         * @param description The error description, {@code null} if not
097         *                    specified.
098         */
099        public ErrorObject(final String code, final String description) {
100        
101                this(code, description, 0, null);
102        }
103
104
105        /**
106         * Creates a new error with the specified code, description and HTTP 
107         * status code.
108         *
109         * @param code           The error code, {@code null} if not specified.
110         * @param description    The error description, {@code null} if not
111         *                       specified.
112         * @param httpStatusCode The HTTP status code, zero if not specified.
113         */
114        public ErrorObject(final String code, final String description, 
115                           final int httpStatusCode) {
116        
117                this(code, description, httpStatusCode, null);
118        }
119
120
121        /**
122         * Creates a new error with the specified code, description, HTTP 
123         * status code and page URI.
124         *
125         * @param code           The error code, {@code null} if not specified.
126         * @param description    The error description, {@code null} if not
127         *                       specified.
128         * @param httpStatusCode The HTTP status code, zero if not specified.
129         * @param uri            The error page URI, {@code null} if not
130         *                       specified.
131         */
132        public ErrorObject(final String code, final String description, 
133                           final int httpStatusCode, final URI uri) {
134        
135                this.code = code;
136                this.description = description;
137                this.httpStatusCode = httpStatusCode;
138                this.uri = uri;
139        }
140
141
142        /**
143         * Gets the error code.
144         *
145         * @return The error code, {@code null} if not specified.
146         */
147        public String getCode() {
148
149                return code;
150        }
151        
152        
153        /**
154         * Gets the error description.
155         *
156         * @return The error description, {@code null} if not specified.
157         */
158        public String getDescription() {
159        
160                return description;
161        }
162
163
164        /**
165         * Sets the error description.
166         *
167         * @param description The error description, {@code null} if not 
168         *                    specified.
169         *
170         * @return A copy of this error with the specified description.
171         */
172        public ErrorObject setDescription(final String description) {
173
174                return new ErrorObject(getCode(), description, getHTTPStatusCode(), getURI());
175        }
176
177
178        /**
179         * Appends the specified text to the error description.
180         *
181         * @param text The text to append to the error description, 
182         *             {@code null} if not specified.
183         *
184         * @return A copy of this error with the specified appended 
185         *         description.
186         */
187        public ErrorObject appendDescription(final String text) {
188
189                String newDescription;
190
191                if (getDescription() != null)
192                        newDescription = getDescription() + text;
193                else
194                        newDescription = text;
195
196                return new ErrorObject(getCode(), newDescription, getHTTPStatusCode(), getURI());
197        }
198
199
200        /**
201         * Gets the HTTP status code.
202         *
203         * @return The HTTP status code, zero if not specified.
204         */
205        public int getHTTPStatusCode() {
206
207                return httpStatusCode;
208        }
209
210
211        /**
212         * Sets the HTTP status code.
213         *
214         * @param httpStatusCode  The HTTP status code, zero if not specified.
215         *
216         * @return A copy of this error with the specified HTTP status code.
217         */
218        public ErrorObject setHTTPStatusCode(final int httpStatusCode) {
219
220                return new ErrorObject(getCode(), getDescription(), httpStatusCode, getURI());
221        }
222
223
224        /**
225         * Gets the error page URI.
226         *
227         * @return The error page URI, {@code null} if not specified.
228         */
229        public URI getURI() {
230
231                return uri;
232        }
233
234
235        /**
236         * Sets the error page URI.
237         *
238         * @param uri The error page URI, {@code null} if not specified.
239         *
240         * @return A copy of this error with the specified page URI.
241         */
242        public ErrorObject setURI(final URI uri) {
243
244                return new ErrorObject(getCode(), getDescription(), getHTTPStatusCode(), uri);
245        }
246
247
248        /**
249         * Returns a JSON object representation of this error object.
250         *
251         * <p>Example:
252         *
253         * <pre>
254         * {
255         *   "error"             : "invalid_grant",
256         *   "error_description" : "Invalid resource owner credentials"
257         * }
258         * </pre>
259         *
260         * @return The JSON object.
261         */
262        public JSONObject toJSONObject() {
263
264                JSONObject o = new JSONObject();
265
266                if (code != null) {
267                        o.put("error", code);
268                }
269
270                if (description != null) {
271                        o.put("error_description", description);
272                }
273
274                if (uri != null) {
275                        o.put("error_uri", uri.toString());
276                }
277
278                return o;
279        }
280        
281        
282        /**
283         * Returns a parameters representation of this error object. Suitable
284         * for URL-encoded error responses.
285         *
286         * @return The parameters.
287         */
288        public Map<String, List<String>> toParameters() {
289                
290                Map<String,List<String>> params = new HashMap<>();
291                
292                if (getCode() != null) {
293                        params.put("error", Collections.singletonList(getCode()));
294                }
295                
296                if (getDescription() != null) {
297                        params.put("error_description", Collections.singletonList(getDescription()));
298                }
299                
300                if (getURI() != null) {
301                        params.put("error_uri", Collections.singletonList(getURI().toString()));
302                }
303                
304                return params;
305        }
306
307
308        /**
309         * @see #getCode
310         */
311        @Override
312        public String toString() {
313
314                return code != null ? code : "null";
315        }
316
317
318        @Override
319        public int hashCode() {
320
321                return code != null ? code.hashCode() : "null".hashCode();
322        }
323
324
325        @Override
326        public boolean equals(final Object object) {
327        
328                return object instanceof ErrorObject &&
329                       this.toString().equals(object.toString());
330        }
331
332
333        /**
334         * Parses an error object from the specified JSON object.
335         *
336         * @param jsonObject The JSON object to parse. Must not be
337         *                   {@code null}.
338         *
339         * @return The error object.
340         */
341        public static ErrorObject parse(final JSONObject jsonObject) {
342
343                String code = null;
344                try {
345                        code = JSONObjectUtils.getString(jsonObject, "error", null);
346                } catch (ParseException e) {
347                        // ignore and continue
348                }
349                
350                String description = null;
351                try {
352                        description = JSONObjectUtils.getString(jsonObject, "error_description", null);
353                } catch (ParseException e) {
354                        // ignore and continue
355                }
356                
357                URI uri = null;
358                try {
359                        uri = JSONObjectUtils.getURI(jsonObject, "error_uri", null);
360                } catch (ParseException e) {
361                        // ignore and continue
362                }
363
364                return new ErrorObject(code, description, 0, uri);
365        }
366        
367        
368        /**
369         * Parses an error object from the specified parameters representation.
370         * Suitable for URL-encoded error responses.
371         *
372         * @param params The parameters. Must not be {@code null}.
373         *
374         * @return The error object.
375         */
376        public static ErrorObject parse(final Map<String, List<String>> params) {
377                
378                String code = MultivaluedMapUtils.getFirstValue(params, "error");
379                String description = MultivaluedMapUtils.getFirstValue(params, "error_description");
380                String uriString = MultivaluedMapUtils.getFirstValue(params, "error_uri");
381                
382                URI uri = null;
383                if (uriString != null) {
384                        try {
385                                uri = new URI(uriString);
386                        } catch (URISyntaxException e) {
387                                // ignore
388                        }
389                }
390                
391                return new ErrorObject(code, description, 0, uri);
392        }
393
394
395        /**
396         * Parses an error object from the specified HTTP response.
397         *
398         * @param httpResponse The HTTP response to parse. Must not be
399         *                     {@code null}.
400         *
401         * @return The error object.
402         */
403        public static ErrorObject parse(final HTTPResponse httpResponse) {
404
405                JSONObject jsonObject;
406                try {
407                        jsonObject = httpResponse.getContentAsJSONObject();
408                } catch (ParseException e) {
409                        return new ErrorObject(null, null, httpResponse.getStatusCode());
410                }
411
412                ErrorObject intermediary = parse(jsonObject);
413
414                return new ErrorObject(
415                        intermediary.getCode(),
416                        intermediary.description,
417                        httpResponse.getStatusCode(),
418                        intermediary.getURI());
419        }
420}