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