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