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