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