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-18), section 4.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 ErrorObject getErrorObject() { 118 119 return error; 120 } 121 122 123 /** 124 * Returns the HTTP response for this client registration error 125 * response. 126 * 127 * <p>Example HTTP response: 128 * 129 * <pre> 130 * HTTP/1.1 400 Bad Request 131 * Content-Type: application/json 132 * Cache-Control: no-store 133 * Pragma: no-cache 134 * 135 * { 136 * "error":"invalid_redirect_uri", 137 * "error_description":"The redirection URI of http://sketchy.example.com is not allowed for this server." 138 * } 139 * </pre> 140 * 141 * @return The HTTP response. 142 */ 143 @Override 144 public HTTPResponse toHTTPResponse() { 145 146 HTTPResponse httpResponse; 147 148 if (error.getHTTPStatusCode() > 0) { 149 httpResponse = new HTTPResponse(error.getHTTPStatusCode()); 150 } else { 151 httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); 152 } 153 154 // Add the WWW-Authenticate header 155 if (error instanceof BearerTokenError) { 156 157 BearerTokenError bte = (BearerTokenError)error; 158 159 httpResponse.setWWWAuthenticate(bte.toWWWAuthenticateHeader()); 160 161 } else { 162 JSONObject jsonObject = new JSONObject(); 163 164 if (error.getCode() != null) 165 jsonObject.put("error", error.getCode()); 166 167 if (error.getDescription() != null) 168 jsonObject.put("error_description", error.getDescription()); 169 170 httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON); 171 172 httpResponse.setContent(jsonObject.toString()); 173 } 174 175 httpResponse.setCacheControl("no-store"); 176 httpResponse.setPragma("no-cache"); 177 178 return httpResponse; 179 } 180 181 182 /** 183 * Parses a client registration error response from the specified HTTP 184 * response. 185 * 186 * <p>Note: The HTTP status code is not checked for matching the error 187 * code semantics. 188 * 189 * @param httpResponse The HTTP response to parse. Its status code must 190 * not be 200 (OK). Must not be {@code null}. 191 * 192 * @throws ParseException If the HTTP response couldn't be parsed to a 193 * client registration error response. 194 */ 195 public static ClientRegistrationErrorResponse parse(final HTTPResponse httpResponse) 196 throws ParseException { 197 198 httpResponse.ensureStatusCodeNotOK(); 199 200 ErrorObject error; 201 202 String wwwAuth = httpResponse.getWWWAuthenticate(); 203 204 if (StringUtils.isNotBlank(wwwAuth)) { 205 206 error = BearerTokenError.parse(wwwAuth); 207 208 } else { 209 210 String code = null; 211 String description = null; 212 213 if (CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) { 214 215 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 216 217 code = JSONObjectUtils.getString(jsonObject, "error"); 218 219 if (jsonObject.containsKey("error_description")) 220 description = JSONObjectUtils.getString(jsonObject, "error_description"); 221 } 222 223 error = new ErrorObject(code, description, httpResponse.getStatusCode()); 224 } 225 226 return new ClientRegistrationErrorResponse(error); 227 } 228}