001 package com.nimbusds.oauth2.sdk; 002 003 004 import java.net.MalformedURLException; 005 import java.net.URL; 006 import java.util.Collections; 007 import java.util.HashSet; 008 import java.util.Set; 009 010 import net.jcip.annotations.Immutable; 011 012 import net.minidev.json.JSONObject; 013 014 import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 015 import com.nimbusds.oauth2.sdk.http.HTTPResponse; 016 import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 017 018 019 /** 020 * OAuth 2.0 Token error response. This class is immutable. 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 * @author Vladimir Dzhuvinov 053 */ 054 @Immutable 055 public 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 // HTTP status 400 156 HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); 157 158 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 159 httpResponse.setCacheControl("no-store"); 160 httpResponse.setPragma("no-cache"); 161 162 httpResponse.setContent(toJSONObject().toString()); 163 164 return httpResponse; 165 } 166 167 168 /** 169 * Parses an OAuth 2.0 Token Error response from the specified JSON 170 * object. 171 * 172 * @param jsonObject The JSON object to parse. Its status code must not 173 * be 200 (OK). Must not be {@code null}. 174 * 175 * @throws ParseException If the JSON object couldn't be parsed to an 176 * OAuth 2.0 Token Error response. 177 */ 178 public static TokenErrorResponse parse(final JSONObject jsonObject) 179 throws ParseException { 180 181 // No error code? 182 if (! jsonObject.containsKey("error")) 183 return new TokenErrorResponse(); 184 185 ErrorObject error = null; 186 187 try { 188 // Parse code 189 String code = JSONObjectUtils.getString(jsonObject, "error"); 190 191 // Parse description 192 String description = null; 193 194 if (jsonObject.containsKey("error_description")) 195 description = JSONObjectUtils.getString(jsonObject, "error_description"); 196 197 // Parse URI 198 URL uri = null; 199 200 if (jsonObject.containsKey("error_uri")) 201 uri = new URL(JSONObjectUtils.getString(jsonObject, "error_uri")); 202 203 204 error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri); 205 206 } catch (ParseException e) { 207 208 throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(), e); 209 210 } catch (MalformedURLException e) { 211 212 throw new ParseException("Invalid error URI: " + e.getMessage(), e); 213 } 214 215 return new TokenErrorResponse(error); 216 } 217 218 219 /** 220 * Parses an OAuth 2.0 Token Error response from the specified HTTP 221 * response. 222 * 223 * @param httpResponse The HTTP response to parse. Its status code must 224 * not be 200 (OK). Must not be {@code null}. 225 * 226 * @throws ParseException If the HTTP response couldn't be parsed to an 227 * OAuth 2.0 Token Error response. 228 */ 229 public static TokenErrorResponse parse(final HTTPResponse httpResponse) 230 throws ParseException { 231 232 httpResponse.ensureStatusCodeNotOK(); 233 234 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 235 236 return parse(jsonObject); 237 } 238 }