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