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