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 056 extends TokenResponse 057 implements ErrorResponse { 058 059 060 /** 061 * The standard OAuth 2.0 errors for an Access Token error response. 062 */ 063 private static final Set<ErrorObject> stdErrors = new HashSet<ErrorObject>(); 064 065 066 static { 067 stdErrors.add(OAuth2Error.INVALID_REQUEST); 068 stdErrors.add(OAuth2Error.INVALID_CLIENT); 069 stdErrors.add(OAuth2Error.INVALID_GRANT); 070 stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT); 071 stdErrors.add(OAuth2Error.UNSUPPORTED_GRANT_TYPE); 072 stdErrors.add(OAuth2Error.INVALID_SCOPE); 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 Collections.unmodifiableSet(stdErrors); 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 ErrorObject getErrorObject() { 122 123 return error; 124 } 125 126 127 /** 128 * Returns the JSON object for this token error response. 129 * 130 * @return The JSON object for this token error response. 131 */ 132 public JSONObject toJSONObject() { 133 134 JSONObject o = new JSONObject(); 135 136 // No error? 137 if (error == null) 138 return o; 139 140 o.put("error", error.getCode()); 141 142 if (error.getDescription() != null) 143 o.put("error_description", error.getDescription()); 144 145 if (error.getURI() != null) 146 o.put("error_uri", error.getURI().toString()); 147 148 return o; 149 } 150 151 152 @Override 153 public HTTPResponse toHTTPResponse() { 154 155 int statusCode = (error != null && error.getHTTPStatusCode() > 0) ? 156 error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST; 157 158 HTTPResponse httpResponse = new HTTPResponse(statusCode); 159 160 if (error == null) 161 return httpResponse; 162 163 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 164 httpResponse.setCacheControl("no-store"); 165 httpResponse.setPragma("no-cache"); 166 167 httpResponse.setContent(toJSONObject().toString()); 168 169 return httpResponse; 170 } 171 172 173 /** 174 * Parses an OAuth 2.0 Token Error response from the specified JSON 175 * object. 176 * 177 * @param jsonObject The JSON object to parse. Its status code must not 178 * be 200 (OK). Must not be {@code null}. 179 * 180 * @throws ParseException If the JSON object couldn't be parsed to an 181 * OAuth 2.0 Token Error response. 182 */ 183 public static TokenErrorResponse parse(final JSONObject jsonObject) 184 throws ParseException { 185 186 // No error code? 187 if (! jsonObject.containsKey("error")) 188 return new TokenErrorResponse(); 189 190 ErrorObject error; 191 192 try { 193 // Parse code 194 String code = JSONObjectUtils.getString(jsonObject, "error"); 195 196 // Parse description 197 String description = null; 198 199 if (jsonObject.containsKey("error_description")) 200 description = JSONObjectUtils.getString(jsonObject, "error_description"); 201 202 // Parse URI 203 URI uri = null; 204 205 if (jsonObject.containsKey("error_uri")) 206 uri = new URI(JSONObjectUtils.getString(jsonObject, "error_uri")); 207 208 209 error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri); 210 211 } catch (ParseException e) { 212 213 throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e); 214 215 } catch (URISyntaxException e) { 216 217 throw new ParseException("Invalid error URI: " + e.getMessage(), e); 218 } 219 220 return new TokenErrorResponse(error); 221 } 222 223 224 /** 225 * Parses an OAuth 2.0 Token Error response from the specified HTTP 226 * response. 227 * 228 * @param httpResponse The HTTP response to parse. Its status code must 229 * not be 200 (OK). Must not be {@code null}. 230 * 231 * @throws ParseException If the HTTP response couldn't be parsed to an 232 * OAuth 2.0 Token Error response. 233 */ 234 public static TokenErrorResponse parse(final HTTPResponse httpResponse) 235 throws ParseException { 236 237 httpResponse.ensureStatusCodeNotOK(); 238 239 if (StringUtils.isNotEmpty(httpResponse.getContent()) && 240 CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) { 241 242 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 243 244 return parse(jsonObject); 245 } 246 247 return new TokenErrorResponse(); 248 } 249}