001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.Set;
024
025import static com.nimbusds.oauth2.sdk.http.HTTPResponse.SC_FORBIDDEN;
026import static com.nimbusds.oauth2.sdk.http.HTTPResponse.SC_UNAUTHORIZED;
027
028import net.jcip.annotations.Immutable;
029
030import com.nimbusds.common.contenttype.ContentType;
031import com.nimbusds.oauth2.sdk.http.HTTPResponse;
032import com.nimbusds.oauth2.sdk.token.BearerTokenError;
033
034
035/**
036 * Token introspection error response.
037 *
038 * <p>Standard errors:
039 *
040 * <ul>
041 *     <li>{@link OAuth2Error#INVALID_REQUEST}
042 *     <li>{@link OAuth2Error#INVALID_CLIENT}
043 *     <li>{@link BearerTokenError#MISSING_TOKEN}
044 *     <li>{@link BearerTokenError#INVALID_REQUEST}
045 *     <li>{@link BearerTokenError#INVALID_TOKEN}
046 *     <li>{@link BearerTokenError#INSUFFICIENT_SCOPE}
047 * </ul>
048 *
049 * <p>Example HTTP response:
050 *
051 * <pre>
052 * HTTP/1.1 401 Unauthorized
053 * WWW-Authenticate: Bearer realm="example.com",
054 *                   error="invalid_token",
055 *                   error_description="The access token expired"
056 * </pre>
057 *
058 * <p>Related specifications:
059 *
060 * <ul>
061 *     <li>OAuth 2.0 Token Introspection (RFC 7662).
062 * </ul>
063 */
064@Immutable
065public class TokenIntrospectionErrorResponse extends TokenIntrospectionResponse implements ErrorResponse {
066
067
068        /**
069         * The standard errors for a token introspection error response.
070         */
071        private static final Set<ErrorObject> STANDARD_ERRORS;
072
073
074        static {
075                Set<ErrorObject> errors = new HashSet<>();
076                errors.add(OAuth2Error.INVALID_REQUEST);
077                errors.add(OAuth2Error.INVALID_CLIENT);
078                errors.add(BearerTokenError.MISSING_TOKEN);
079                errors.add(BearerTokenError.INVALID_REQUEST);
080                errors.add(BearerTokenError.INVALID_TOKEN);
081                errors.add(BearerTokenError.INSUFFICIENT_SCOPE);
082                STANDARD_ERRORS = Collections.unmodifiableSet(errors);
083        }
084
085
086        /**
087         * Gets the standard  errors for a token introspection error response.
088         *
089         * @return The standard errors, as a read-only set.
090         */
091        public static Set<ErrorObject> getStandardErrors() {
092
093                return STANDARD_ERRORS;
094        }
095
096
097        /**
098         * The error.
099         */
100        private final ErrorObject error;
101
102
103        /**
104         * Creates a new token introspection error response.
105         *
106         * @param error The error, {@code null} if not specified.
107         */
108        public TokenIntrospectionErrorResponse(final ErrorObject error) {
109
110                this.error = error;
111        }
112
113
114        @Override
115        public ErrorObject getErrorObject() {
116
117                return error;
118        }
119
120
121        @Override
122        public boolean indicatesSuccess() {
123
124                return false;
125        }
126
127
128        @Override
129        public HTTPResponse toHTTPResponse() {
130
131                // Determine HTTP status code
132                int statusCode = error != null && error.getHTTPStatusCode() > 0 ?
133                        error.getHTTPStatusCode() : HTTPResponse.SC_BAD_REQUEST;
134
135                HTTPResponse httpResponse = new HTTPResponse(statusCode);
136
137                if (error == null) {
138                        return httpResponse;
139                }
140
141                // Print error object if available
142                if (error instanceof BearerTokenError) {
143                        httpResponse.setWWWAuthenticate(((BearerTokenError) error).toWWWAuthenticateHeader());
144                }
145
146                httpResponse.setEntityContentType(ContentType.APPLICATION_JSON);
147                httpResponse.setCacheControl("no-store");
148                httpResponse.setPragma("no-cache");
149                httpResponse.setContent(error.toJSONObject().toJSONString());
150
151                return httpResponse;
152        }
153
154
155        /**
156         * Parses a token introspection error response from the specified HTTP
157         * response.
158         *
159         * @param httpResponse The HTTP response to parse. Its status code must
160         *                     not be 200 (OK). Must not be {@code null}.
161         *
162         * @return The token introspection error response.
163         *
164         * @throws ParseException If the HTTP response couldn't be parsed to a
165         *                        token introspection error response.
166         */
167        public static TokenIntrospectionErrorResponse parse(final HTTPResponse httpResponse)
168                throws ParseException {
169
170                httpResponse.ensureStatusCodeNotOK();
171
172                String wwwAuth = httpResponse.getWWWAuthenticate();
173
174                if ((httpResponse.getStatusCode() == SC_UNAUTHORIZED || httpResponse.getStatusCode() == SC_FORBIDDEN)
175                        && wwwAuth != null && wwwAuth.toLowerCase().startsWith("bearer")) {
176
177                        try {
178                                return new TokenIntrospectionErrorResponse(BearerTokenError.parse(httpResponse.getWWWAuthenticate()));
179                        } catch (ParseException e) {
180                                // try generic error parse ...
181                        }
182                }
183
184                return new TokenIntrospectionErrorResponse(ErrorObject.parse(httpResponse));
185        }
186}