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