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}