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.setBody(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}