001package com.nimbusds.openid.connect.sdk; 002 003 004import java.util.Collections; 005import java.util.HashSet; 006import java.util.Set; 007 008import com.nimbusds.oauth2.sdk.ErrorObject; 009import com.nimbusds.oauth2.sdk.ErrorResponse; 010import com.nimbusds.oauth2.sdk.ParseException; 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; 015import org.apache.commons.lang3.StringUtils; 016 017 018/** 019 * UserInfo error response. 020 * 021 * <p>Standard OAuth 2.0 Bearer Token errors: 022 * 023 * <ul> 024 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#MISSING_TOKEN} 025 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_REQUEST} 026 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_TOKEN} 027 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INSUFFICIENT_SCOPE} 028 * </ul> 029 * 030 * <p>Example HTTP response: 031 * 032 * <pre> 033 * HTTP/1.1 401 Unauthorized 034 * WWW-Authenticate: Bearer realm="example.com", 035 * error="invalid_token", 036 * error_description="The access token expired" 037 * </pre> 038 * 039 * <p>Related specifications: 040 * 041 * <ul> 042 * <li>OpenID Connect Core 1.0, section 5.3.3. 043 * <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1. 044 * </ul> 045 */ 046@Immutable 047public class UserInfoErrorResponse 048 extends UserInfoResponse 049 implements ErrorResponse { 050 051 052 /** 053 * Gets the standard errors for a UserInfo error response. 054 * 055 * @return The standard errors, as a read-only set. 056 */ 057 public static Set<BearerTokenError> getStandardErrors() { 058 059 Set<BearerTokenError> stdErrors = new HashSet<>(); 060 stdErrors.add(BearerTokenError.MISSING_TOKEN); 061 stdErrors.add(BearerTokenError.INVALID_REQUEST); 062 stdErrors.add(BearerTokenError.INVALID_TOKEN); 063 stdErrors.add(BearerTokenError.INSUFFICIENT_SCOPE); 064 065 return Collections.unmodifiableSet(stdErrors); 066 } 067 068 069 /** 070 * The underlying error. 071 */ 072 private final ErrorObject error; 073 074 075 /** 076 * Creates a new UserInfo error response. No OAuth 2.0 bearer token 077 * error / general error object is specified. 078 */ 079 private UserInfoErrorResponse() { 080 081 error = null; 082 } 083 084 085 /** 086 * Creates a new UserInfo error response indicating a bearer token 087 * error. 088 * 089 * @param error The OAuth 2.0 bearer token error. Should match one of 090 * the {@link #getStandardErrors standard errors} for a 091 * UserInfo error response. Must not be {@code null}. 092 */ 093 public UserInfoErrorResponse(final BearerTokenError error) { 094 095 this((ErrorObject) error); 096 } 097 098 099 /** 100 * Creates a new UserInfo error response indicating a general error. 101 * 102 * @param error The error. Must not be {@code null}. 103 */ 104 public UserInfoErrorResponse(final ErrorObject error) { 105 106 if (error == null) 107 throw new IllegalArgumentException("The error must not be null"); 108 109 this.error = error; 110 } 111 112 113 @Override 114 public boolean indicatesSuccess() { 115 116 return false; 117 } 118 119 120 @Override 121 public ErrorObject getErrorObject() { 122 123 return error; 124 } 125 126 127 /** 128 * Returns the HTTP response for this UserInfo error response. 129 * 130 * <p>Example HTTP response: 131 * 132 * <pre> 133 * HTTP/1.1 401 Unauthorized 134 * WWW-Authenticate: Bearer realm="example.com", 135 * error="invalid_token", 136 * error_description="The access token expired" 137 * </pre> 138 * 139 * @return The HTTP response matching this UserInfo error response. 140 */ 141 @Override 142 public HTTPResponse toHTTPResponse() { 143 144 HTTPResponse httpResponse; 145 146 if (error != null && error.getHTTPStatusCode() > 0) { 147 httpResponse = new HTTPResponse(error.getHTTPStatusCode()); 148 } else { 149 httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); 150 } 151 152 // Add the WWW-Authenticate header 153 if (error instanceof BearerTokenError) { 154 httpResponse.setWWWAuthenticate(((BearerTokenError) error).toWWWAuthenticateHeader()); 155 } else if (error != null){ 156 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 157 httpResponse.setContent(error.toJSONObject().toJSONString()); 158 } 159 160 return httpResponse; 161 } 162 163 164 /** 165 * Parses a UserInfo error response from the specified HTTP response 166 * {@code WWW-Authenticate} header. 167 * 168 * @param wwwAuth The {@code WWW-Authenticate} header value to parse. 169 * Must not be {@code null}. 170 * 171 * @throws ParseException If the {@code WWW-Authenticate} header value 172 * couldn't be parsed to a UserInfo error 173 * response. 174 */ 175 public static UserInfoErrorResponse parse(final String wwwAuth) 176 throws ParseException { 177 178 BearerTokenError error = BearerTokenError.parse(wwwAuth); 179 180 return new UserInfoErrorResponse(error); 181 } 182 183 184 /** 185 * Parses a UserInfo error response from the specified HTTP response. 186 * 187 * <p>Note: The HTTP status code is not checked for matching the error 188 * code semantics. 189 * 190 * @param httpResponse The HTTP response to parse. Its status code must 191 * not be 200 (OK). Must not be {@code null}. 192 * 193 * @throws ParseException If the HTTP response couldn't be parsed to a 194 * UserInfo error response. 195 */ 196 public static UserInfoErrorResponse parse(final HTTPResponse httpResponse) 197 throws ParseException { 198 199 httpResponse.ensureStatusCodeNotOK(); 200 201 String wwwAuth = httpResponse.getWWWAuthenticate(); 202 203 if (StringUtils.isNotBlank(wwwAuth)) { 204 // Bearer token error? 205 return parse(wwwAuth); 206 } 207 208 // Other error? 209 return new UserInfoErrorResponse(ErrorObject.parse(httpResponse)); 210 } 211}