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 net.jcip.annotations.Immutable; 011 012import org.apache.commons.lang3.StringUtils; 013 014import net.minidev.json.JSONObject; 015 016import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 017import com.nimbusds.oauth2.sdk.http.HTTPResponse; 018import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 019 020 021/** 022 * OAuth 2.0 Token error response. 023 * 024 * <p>Standard token errors: 025 * 026 * <ul> 027 * <li>{@link OAuth2Error#INVALID_REQUEST} 028 * <li>{@link OAuth2Error#INVALID_CLIENT} 029 * <li>{@link OAuth2Error#INVALID_GRANT} 030 * <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT} 031 * <li>{@link OAuth2Error#UNSUPPORTED_GRANT_TYPE} 032 * <li>{@link OAuth2Error#INVALID_SCOPE} 033 * </ul> 034 * 035 * <p>Example HTTP response: 036 * 037 * <pre> 038 * HTTP/1.1 400 Bad Request 039 * Content-Type: application/json 040 * Cache-Control: no-store 041 * Pragma: no-cache 042 * 043 * { 044 * "error": "invalid_request" 045 * } 046 * </pre> 047 * 048 * <p>Related specifications: 049 * 050 * <ul> 051 * <li>OAuth 2.0 (RFC 6749), section 5.2. 052 * </ul> 053 */ 054@Immutable 055public class TokenErrorResponse extends TokenResponse implements ErrorResponse { 056 057 058 /** 059 * The standard OAuth 2.0 errors for an Access Token error response. 060 */ 061 private static final Set<ErrorObject> STANDARD_ERRORS; 062 063 064 static { 065 Set<ErrorObject> errors = new HashSet<>(); 066 errors.add(OAuth2Error.INVALID_REQUEST); 067 errors.add(OAuth2Error.INVALID_CLIENT); 068 errors.add(OAuth2Error.INVALID_GRANT); 069 errors.add(OAuth2Error.UNAUTHORIZED_CLIENT); 070 errors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE); 071 errors.add(OAuth2Error.INVALID_SCOPE); 072 STANDARD_ERRORS = Collections.unmodifiableSet(errors); 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 STANDARD_ERRORS; 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 boolean indicatesSuccess() { 122 123 return false; 124 } 125 126 127 @Override 128 public ErrorObject getErrorObject() { 129 130 return error; 131 } 132 133 134 /** 135 * Returns the JSON object for this token error response. 136 * 137 * @return The JSON object for this token error response. 138 */ 139 public JSONObject toJSONObject() { 140 141 JSONObject o = new JSONObject(); 142 143 // No error? 144 if (error == null) 145 return o; 146 147 o.put("error", error.getCode()); 148 149 if (error.getDescription() != null) 150 o.put("error_description", error.getDescription()); 151 152 if (error.getURI() != null) 153 o.put("error_uri", error.getURI().toString()); 154 155 return o; 156 } 157 158 159 @Override 160 public HTTPResponse toHTTPResponse() { 161 162 int statusCode = (error != null && error.getHTTPStatusCode() > 0) ? 163 error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST; 164 165 HTTPResponse httpResponse = new HTTPResponse(statusCode); 166 167 if (error == null) 168 return httpResponse; 169 170 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 171 httpResponse.setCacheControl("no-store"); 172 httpResponse.setPragma("no-cache"); 173 174 httpResponse.setContent(toJSONObject().toString()); 175 176 return httpResponse; 177 } 178 179 180 /** 181 * Parses an OAuth 2.0 Token Error response from the specified JSON 182 * object. 183 * 184 * @param jsonObject The JSON object to parse. Its status code must not 185 * be 200 (OK). Must not be {@code null}. 186 * 187 * @throws ParseException If the JSON object couldn't be parsed to an 188 * OAuth 2.0 Token Error response. 189 */ 190 public static TokenErrorResponse parse(final JSONObject jsonObject) 191 throws ParseException { 192 193 // No error code? 194 if (! jsonObject.containsKey("error")) 195 return new TokenErrorResponse(); 196 197 ErrorObject error; 198 199 try { 200 // Parse code 201 String code = JSONObjectUtils.getString(jsonObject, "error"); 202 203 // Parse description 204 String description = null; 205 206 if (jsonObject.containsKey("error_description")) 207 description = JSONObjectUtils.getString(jsonObject, "error_description"); 208 209 // Parse URI 210 URI uri = null; 211 212 if (jsonObject.containsKey("error_uri")) 213 uri = new URI(JSONObjectUtils.getString(jsonObject, "error_uri")); 214 215 216 error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri); 217 218 } catch (ParseException e) { 219 220 throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e); 221 222 } catch (URISyntaxException e) { 223 224 throw new ParseException("Invalid error URI: " + e.getMessage(), e); 225 } 226 227 return new TokenErrorResponse(error); 228 } 229 230 231 /** 232 * Parses an OAuth 2.0 Token Error response from the specified HTTP 233 * response. 234 * 235 * @param httpResponse The HTTP response to parse. Its status code must 236 * not be 200 (OK). Must not be {@code null}. 237 * 238 * @throws ParseException If the HTTP response couldn't be parsed to an 239 * OAuth 2.0 Token Error response. 240 */ 241 public static TokenErrorResponse parse(final HTTPResponse httpResponse) 242 throws ParseException { 243 244 httpResponse.ensureStatusCodeNotOK(); 245 return new TokenErrorResponse(ErrorObject.parse(httpResponse)); 246 } 247}