001 package com.nimbusds.oauth2.sdk.client; 002 003 004 import java.util.Collections; 005 import java.util.HashSet; 006 import java.util.Set; 007 008 import net.jcip.annotations.Immutable; 009 010 import org.apache.commons.lang3.StringUtils; 011 012 import net.minidev.json.JSONObject; 013 014 import com.nimbusds.oauth2.sdk.ErrorObject; 015 import com.nimbusds.oauth2.sdk.ErrorResponse; 016 import com.nimbusds.oauth2.sdk.ParseException; 017 import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 018 import com.nimbusds.oauth2.sdk.http.HTTPResponse; 019 import com.nimbusds.oauth2.sdk.token.BearerTokenError; 020 import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 021 022 023 /** 024 * Client registration error response. This class is immutable. 025 * 026 * <p>Standard errors: 027 * 028 * <ul> 029 * <li>OAuth 2.0 Bearer Token errors: 030 * <ul> 031 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#MISSING_TOKEN} 032 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_REQUEST} 033 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_TOKEN} 034 * <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INSUFFICIENT_SCOPE} 035 * </ul> 036 * <li>OpenID Connect specific errors: 037 * <ul> 038 * <li>{@link RegistrationError#INVALID_REDIRECT_URI} 039 * <li>{@link RegistrationError#INVALID_CLIENT_METADATA} 040 * </ul> 041 * </ul> 042 * 043 * <p>Example HTTP response: 044 * 045 * <pre> 046 * HTTP/1.1 400 Bad Request 047 * Content-Type: application/json 048 * Cache-Control: no-store 049 * Pragma: no-cache 050 * 051 * { 052 * "error":"invalid_redirect_uri", 053 * "error_description":"The redirect URI of http://sketchy.example.com is not allowed for this server." 054 * } 055 * </pre> 056 * 057 * <p>Related specifications: 058 * 059 * <ul> 060 * <li>OAuth 2.0 Dynamic Client Registration Protocol 061 * (draft-ietf-oauth-dyn-reg-12), section 5.2. 062 * <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1. 063 * </ul> 064 * 065 * @author Vladimir Dzhuvinov 066 */ 067 @Immutable 068 public class ClientRegistrationErrorResponse 069 extends ClientRegistrationResponse 070 implements ErrorResponse { 071 072 073 /** 074 * Gets the standard errors for a client registration error response. 075 * 076 * @return The standard errors, as a read-only set. 077 */ 078 public static Set<ErrorObject> getStandardErrors() { 079 080 Set<ErrorObject> stdErrors = new HashSet<ErrorObject>(); 081 stdErrors.add(BearerTokenError.MISSING_TOKEN); 082 stdErrors.add(BearerTokenError.INVALID_REQUEST); 083 stdErrors.add(BearerTokenError.INVALID_TOKEN); 084 stdErrors.add(BearerTokenError.INSUFFICIENT_SCOPE); 085 stdErrors.add(RegistrationError.INVALID_REDIRECT_URI); 086 stdErrors.add(RegistrationError.INVALID_CLIENT_METADATA); 087 088 return Collections.unmodifiableSet(stdErrors); 089 } 090 091 092 /** 093 * The underlying error. 094 */ 095 private final ErrorObject error; 096 097 098 /** 099 * Creates a new client registration error response. 100 * 101 * @param error The error. Should match one of the 102 * {@link #getStandardErrors standard errors} for a client 103 * registration error response. Must not be {@code null}. 104 */ 105 public ClientRegistrationErrorResponse(final ErrorObject error) { 106 107 if (error == null) 108 throw new IllegalArgumentException("The error must not be null"); 109 110 this.error = error; 111 } 112 113 114 @Override 115 public ErrorObject getErrorObject() { 116 117 return error; 118 } 119 120 121 /** 122 * Returns the HTTP response for this client registration error 123 * response. 124 * 125 * <p>Example HTTP response: 126 * 127 * <pre> 128 * HTTP/1.1 400 Bad Request 129 * Content-Type: application/json 130 * Cache-Control: no-store 131 * Pragma: no-cache 132 * 133 * { 134 * "error":"invalid_redirect_uri", 135 * "error_description":"The redirect URI of http://sketchy.example.com is not allowed for this server." 136 * } 137 * </pre> 138 * 139 * @return The HTTP response. 140 */ 141 @Override 142 public HTTPResponse toHTTPResponse() { 143 144 HTTPResponse httpResponse = null; 145 146 if (error.getHTTPStatusCode() > 0) 147 httpResponse = new HTTPResponse(error.getHTTPStatusCode()); 148 else 149 httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); 150 151 // Add the WWW-Authenticate header 152 if (error != null && error instanceof BearerTokenError) { 153 154 BearerTokenError bte = (BearerTokenError)error; 155 156 httpResponse.setWWWAuthenticate(bte.toWWWAuthenticateHeader()); 157 } 158 else { 159 JSONObject jsonObject = new JSONObject(); 160 161 if (error.getCode() != null) 162 jsonObject.put("error", error.getCode()); 163 164 if (error.getDescription() != null) 165 jsonObject.put("error_description", error.getDescription()); 166 167 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 168 169 httpResponse.setContent(jsonObject.toString()); 170 } 171 172 httpResponse.setCacheControl("no-store"); 173 httpResponse.setPragma("no-cache"); 174 175 return httpResponse; 176 } 177 178 179 /** 180 * Parses a client registration error response from the specified HTTP 181 * response. 182 * 183 * <p>Note: The HTTP status code is not checked for matching the error 184 * code semantics. 185 * 186 * @param httpResponse The HTTP response to parse. Its status code must 187 * not be 200 (OK). Must not be {@code null}. 188 * 189 * @throws ParseException If the HTTP response couldn't be parsed to a 190 * client registration error response. 191 */ 192 public static ClientRegistrationErrorResponse parse(final HTTPResponse httpResponse) 193 throws ParseException { 194 195 httpResponse.ensureStatusCodeNotOK(); 196 197 ErrorObject error = null; 198 199 String wwwAuth = httpResponse.getWWWAuthenticate(); 200 201 if (StringUtils.isNotBlank(wwwAuth)) { 202 203 error = BearerTokenError.parse(wwwAuth); 204 } 205 else { 206 String code = null; 207 String description = null; 208 209 if (CommonContentTypes.APPLICATION_JSON == httpResponse.getContentType()) { 210 211 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 212 213 code = JSONObjectUtils.getString(jsonObject, "error"); 214 215 if (jsonObject.containsKey("error_description")) 216 description = JSONObjectUtils.getString(jsonObject, "error_description"); 217 } 218 219 error = new ErrorObject(code, description, httpResponse.getStatusCode()); 220 } 221 222 return new ClientRegistrationErrorResponse(error); 223 } 224 }