001package com.nimbusds.oauth2.sdk; 002 003 004import java.util.Collections; 005import java.util.HashSet; 006import java.util.Set; 007 008import static com.nimbusds.oauth2.sdk.http.HTTPResponse.SC_FORBIDDEN; 009import static com.nimbusds.oauth2.sdk.http.HTTPResponse.SC_UNAUTHORIZED; 010 011import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 012import com.nimbusds.oauth2.sdk.http.HTTPResponse; 013import com.nimbusds.oauth2.sdk.token.BearerTokenError; 014import net.jcip.annotations.Immutable; 015 016 017/** 018 * Token introspection error response. 019 * 020 * <p>Standard errors: 021 * 022 * <ul> 023 * <li>{@link OAuth2Error#INVALID_REQUEST} 024 * <li>{@link OAuth2Error#INVALID_CLIENT} 025 * <li>{@link BearerTokenError#MISSING_TOKEN} 026 * <li>{@link BearerTokenError#INVALID_REQUEST} 027 * <li>{@link BearerTokenError#INVALID_TOKEN} 028 * <li>{@link BearerTokenError#INSUFFICIENT_SCOPE} 029 * </ul> 030 * 031 * <p>Example HTTP response: 032 * 033 * <pre> 034 * HTTP/1.1 401 Unauthorized 035 * WWW-Authenticate: Bearer realm="example.com", 036 * error="invalid_token", 037 * error_description="The access token expired" 038 * </pre> 039 * 040 * <p>Related specifications: 041 * 042 * <ul> 043 * <li>OAuth 2.0 Token Introspection (RFC 7662). 044 * </ul> 045 */ 046@Immutable 047public class TokenIntrospectionErrorResponse extends TokenIntrospectionResponse implements ErrorResponse { 048 049 050 /** 051 * The standard errors for a token introspection error response. 052 */ 053 private static final Set<ErrorObject> STANDARD_ERRORS; 054 055 056 static { 057 Set<ErrorObject> errors = new HashSet<>(); 058 errors.add(OAuth2Error.INVALID_REQUEST); 059 errors.add(OAuth2Error.INVALID_CLIENT); 060 errors.add(BearerTokenError.MISSING_TOKEN); 061 errors.add(BearerTokenError.INVALID_REQUEST); 062 errors.add(BearerTokenError.INVALID_TOKEN); 063 errors.add(BearerTokenError.INSUFFICIENT_SCOPE); 064 STANDARD_ERRORS = Collections.unmodifiableSet(errors); 065 } 066 067 068 /** 069 * Gets the standard errors for a token introspection error response. 070 * 071 * @return The standard errors, as a read-only set. 072 */ 073 public static Set<ErrorObject> getStandardErrors() { 074 075 return STANDARD_ERRORS; 076 } 077 078 079 /** 080 * The error. 081 */ 082 private final ErrorObject error; 083 084 085 /** 086 * Creates a new token introspection error response. 087 * 088 * @param error The error, {@code null} if not specified. 089 */ 090 public TokenIntrospectionErrorResponse(final ErrorObject error) { 091 092 this.error = error; 093 } 094 095 096 @Override 097 public ErrorObject getErrorObject() { 098 099 return error; 100 } 101 102 103 @Override 104 public boolean indicatesSuccess() { 105 106 return false; 107 } 108 109 110 @Override 111 public HTTPResponse toHTTPResponse() { 112 113 // Determine HTTP status code 114 int statusCode = error != null && error.getHTTPStatusCode() > 0 ? 115 error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST; 116 117 HTTPResponse httpResponse = new HTTPResponse(statusCode); 118 119 if (error == null) { 120 return httpResponse; 121 } 122 123 // Print error object if available 124 if (error instanceof BearerTokenError) { 125 httpResponse.setWWWAuthenticate(((BearerTokenError) error).toWWWAuthenticateHeader()); 126 } 127 128 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 129 httpResponse.setCacheControl("no-store"); 130 httpResponse.setPragma("no-cache"); 131 httpResponse.setContent(error.toJSONObject().toJSONString()); 132 133 return httpResponse; 134 } 135 136 137 /** 138 * Parses a token introspection error response from the specified HTTP 139 * response. 140 * 141 * @param httpResponse The HTTP response to parse. Its status code must 142 * not be 200 (OK). Must not be {@code null}. 143 * 144 * @throws ParseException If the HTTP response couldn't be parsed to a 145 * token introspection error response. 146 */ 147 public static TokenIntrospectionErrorResponse parse(final HTTPResponse httpResponse) 148 throws ParseException { 149 150 httpResponse.ensureStatusCodeNotOK(); 151 152 String wwwAuth = httpResponse.getWWWAuthenticate(); 153 154 if ((httpResponse.getStatusCode() == SC_UNAUTHORIZED || httpResponse.getStatusCode() == SC_FORBIDDEN) 155 && wwwAuth != null && wwwAuth.toLowerCase().startsWith("bearer")) { 156 157 try { 158 return new TokenIntrospectionErrorResponse(BearerTokenError.parse(httpResponse.getWWWAuthenticate())); 159 } catch (ParseException e) { 160 // try generic error parse ... 161 } 162 } 163 164 return new TokenIntrospectionErrorResponse(ErrorObject.parse(httpResponse)); 165 } 166}