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