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 * @return The token introspection error response. 162 * 163 * @throws ParseException If the HTTP response couldn't be parsed to a 164 * token introspection error response. 165 */ 166 public static TokenIntrospectionErrorResponse parse(final HTTPResponse httpResponse) 167 throws ParseException { 168 169 httpResponse.ensureStatusCodeNotOK(); 170 171 String wwwAuth = httpResponse.getWWWAuthenticate(); 172 173 if ((httpResponse.getStatusCode() == SC_UNAUTHORIZED || httpResponse.getStatusCode() == SC_FORBIDDEN) 174 && wwwAuth != null && wwwAuth.toLowerCase().startsWith("bearer")) { 175 176 try { 177 return new TokenIntrospectionErrorResponse(BearerTokenError.parse(httpResponse.getWWWAuthenticate())); 178 } catch (ParseException e) { 179 // try generic error parse ... 180 } 181 } 182 183 return new TokenIntrospectionErrorResponse(ErrorObject.parse(httpResponse)); 184 } 185}