diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java index 445c5cfb80e..8d6eb165cb1 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java @@ -11,6 +11,7 @@ import com.google.common.collect.Lists; import io.jans.agama.model.EngineConfig; import io.jans.as.model.common.*; +import io.jans.as.model.configuration.rate.RateLimitConfig; import io.jans.as.model.crypto.signature.SignatureAlgorithm; import io.jans.as.model.error.ErrorHandlingMethod; import io.jans.as.model.jwk.KeySelectionStrategy; @@ -746,12 +747,6 @@ public class AppConfiguration implements Configuration { @DocProperty(description = "Authorization challenge session lifetime in seconds") private Integer authorizationChallengeSessionLifetimeInSeconds; - @DocProperty(description = "Request count limit - for /register endpoint (Rate Limit)") - private Integer rateLimitRegistrationRequestCount; - - @DocProperty(description = "Period in seconds limit - for /register endpoint (Rate Limit)") - private Integer rateLimitRegistrationPeriodInSeconds; - // Token Exchange @DocProperty(description = "", defaultValue = "false") private Boolean rotateDeviceSecret = false; @@ -970,6 +965,9 @@ public class AppConfiguration implements Configuration { @DocProperty(description = "DCR SSA Validation configurations used to perform validation of SSA or DCR. Only needed if softwareStatementValidationType=builtin") private List dcrSsaValidationConfigs; + @DocProperty(description = "Rate Limit Configuration") + private RateLimitConfig rateLimitConfiguration; + @DocProperty(description = "SSA Configuration") private SsaConfiguration ssaConfiguration; @@ -1115,24 +1113,6 @@ public void setReturnDeviceSecretFromAuthzEndpoint(Boolean returnDeviceSecretFro this.returnDeviceSecretFromAuthzEndpoint = returnDeviceSecretFromAuthzEndpoint; } - public Integer getRateLimitRegistrationRequestCount() { - return rateLimitRegistrationRequestCount; - } - - public AppConfiguration setRateLimitRegistrationRequestCount(Integer rateLimitRegistrationRequestCount) { - this.rateLimitRegistrationRequestCount = rateLimitRegistrationRequestCount; - return this; - } - - public Integer getRateLimitRegistrationPeriodInSeconds() { - return rateLimitRegistrationPeriodInSeconds; - } - - public AppConfiguration setRateLimitRegistrationPeriodInSeconds(Integer rateLimitRegistrationPeriodInSeconds) { - this.rateLimitRegistrationPeriodInSeconds = rateLimitRegistrationPeriodInSeconds; - return this; - } - public Integer getAuthorizationChallengeSessionLifetimeInSeconds() { if (authorizationChallengeSessionLifetimeInSeconds == null) { authorizationChallengeSessionLifetimeInSeconds = DEFAULT_AUTHORIZATION_CHALLENGE_SESSION_LIFETIME; @@ -3803,6 +3783,14 @@ public void setSsaConfiguration(SsaConfiguration ssaConfiguration) { this.ssaConfiguration = ssaConfiguration; } + public RateLimitConfig getRateLimitConfiguration() { + return rateLimitConfiguration; + } + + public void setRateLimitConfiguration(RateLimitConfig rateLimitConfiguration) { + this.rateLimitConfiguration = rateLimitConfiguration; + } + public Boolean getAuthorizationChallengeShouldGenerateSession() { if (authorizationChallengeShouldGenerateSession == null) authorizationChallengeShouldGenerateSession = false; return authorizationChallengeShouldGenerateSession; diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeyExtractor.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeyExtractor.java new file mode 100644 index 00000000000..78fba423b46 --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeyExtractor.java @@ -0,0 +1,82 @@ +package io.jans.as.model.configuration.rate; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class KeyExtractor { + + private KeySource source; + private List parameterNames = new ArrayList<>(); + + public KeyExtractor() { + } + + @JsonCreator + public KeyExtractor(@JsonProperty("source") KeySource source, @JsonProperty("parameterNames") List parameterNames) { + setSource(source); + setParameterNames(parameterNames); + } + + @JsonProperty("source") + public KeySource getSource() { + return source; + } + + @JsonProperty("source") + public void setSource(KeySource source) { + this.source = source; + } + + @JsonProperty("parameterNames") + public List getParameterNames() { + return parameterNames == null ? Collections.emptyList() : Collections.unmodifiableList(parameterNames); + } + + @JsonProperty("parameterNames") + public void setParameterNames(List parameterNames) { + // Defensive copy + filter null/blank + List safe = new ArrayList<>(); + if (parameterNames != null) { + for (String p : parameterNames) { + if (p == null) continue; + String v = p.trim(); + if (!v.isEmpty()) safe.add(v); + } + } + this.parameterNames = safe; + } + + public boolean isWellFormed() { + return source != null && !getParameterNames().isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof KeyExtractor)) return false; + KeyExtractor that = (KeyExtractor) o; + return source == that.source && Objects.equals(getParameterNames(), that.getParameterNames()); + } + + @Override + public int hashCode() { + return Objects.hash(source, getParameterNames()); + } + + @Override + public String toString() { + return "KeyExtractor{" + + "source=" + source + + ", parameterNames=" + getParameterNames() + + '}'; + } +} diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeySource.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeySource.java new file mode 100644 index 00000000000..747d472d719 --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/KeySource.java @@ -0,0 +1,44 @@ +package io.jans.as.model.configuration.rate; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Where to extract the key from. + *

+ * Defensive behavior: + * - Unknown values deserialize to {@link #UNKNOWN} instead of failing. + * - Serialization uses the json value (lower-case). + */ +public enum KeySource { + BODY("body"), + HEADER("header"), + QUERY("query"), + UNKNOWN("unknown"); + + private final String jsonValue; + + KeySource(String jsonValue) { + this.jsonValue = jsonValue; + } + + @JsonCreator + public static KeySource fromJson(String value) { + if (value == null) return null; // preserve null if field absent + String v = value.trim(); + if (v.isEmpty()) return null; + + for (KeySource s : values()) { + if (s.jsonValue.equalsIgnoreCase(v)) { + return s; + } + } + // Defensive: don't hard-fail on new/typo values + return UNKNOWN; + } + + @JsonValue + public String toJson() { + return jsonValue; + } +} diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitConfig.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitConfig.java new file mode 100644 index 00000000000..5d19d5b40d0 --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitConfig.java @@ -0,0 +1,68 @@ +package io.jans.as.model.configuration.rate; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RateLimitConfig { + + private List rateLimitRules = new ArrayList<>(); + private boolean rateLoggingEnabled = false; + + public RateLimitConfig() { + } + + @JsonCreator + public RateLimitConfig(@JsonProperty("rateLimitRules") List rateLimitRules) { + setRateLimitRules(rateLimitRules); + } + + @JsonProperty("rateLoggingEnabled") + public boolean isRateLoggingEnabled() { + return rateLoggingEnabled; + } + + @JsonProperty("rateLoggingEnabled") + public void setRateLoggingEnabled(boolean rateLoggingEnabled) { + this.rateLoggingEnabled = rateLoggingEnabled; + } + + @JsonProperty("rateLimitRules") + public List getRateLimitRules() { + return rateLimitRules == null ? Collections.emptyList() : Collections.unmodifiableList(rateLimitRules); + } + + @JsonProperty("rateLimitRules") + public void setRateLimitRules(List rateLimitRules) { + this.rateLimitRules = (rateLimitRules == null) ? new ArrayList<>() : new ArrayList<>(rateLimitRules); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RateLimitConfig)) return false; + RateLimitConfig that = (RateLimitConfig) o; + return Objects.equals(getRateLimitRules(), that.getRateLimitRules()); + } + + @Override + public int hashCode() { + return Objects.hash(getRateLimitRules()); + } + + @Override + public String toString() { + return "RateLimitConfig{" + + "rateLimitRules=" + getRateLimitRules() + + "rateLoggingEnabled=" + rateLoggingEnabled + + '}'; + } +} diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitRule.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitRule.java new file mode 100644 index 00000000000..7f0dc65ea51 --- /dev/null +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/rate/RateLimitRule.java @@ -0,0 +1,144 @@ +package io.jans.as.model.configuration.rate; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RateLimitRule { + + private String path; + private List methods = new ArrayList<>(); + private Integer requestCount; + private Integer periodInSeconds; + private List keyExtractors = new ArrayList<>(); + + public RateLimitRule() { + } + + @JsonCreator + public RateLimitRule( + @JsonProperty("path") String path, + @JsonProperty("methods") List methods, + @JsonProperty("requestCount") Integer requestCount, + @JsonProperty("periodInSeconds") Integer periodInSeconds, + @JsonProperty("keyExtractors") List keyExtractors + ) { + setPath(path); + setMethods(methods); + setRequestCount(requestCount); + setPeriodInSeconds(periodInSeconds); + setKeyExtractors(keyExtractors); + } + + @JsonProperty("path") + public String getPath() { + return path; + } + + @JsonProperty("path") + public void setPath(String path) { + this.path = (path == null || path.trim().isEmpty()) ? null : path.trim(); + } + + @JsonProperty("methods") + public List getMethods() { + return methods == null ? Collections.emptyList() : Collections.unmodifiableList(methods); + } + + @JsonProperty("methods") + public void setMethods(List methods) { + // Defensive copy + filter null/blank + List safe = new ArrayList<>(); + if (methods != null) { + for (String m : methods) { + if (m == null) continue; + String v = m.trim(); + if (!v.isEmpty()) safe.add(v); + } + } + this.methods = safe; + } + + @JsonProperty("requestCount") + public Integer getRequestCount() { + return requestCount; + } + + @JsonProperty("requestCount") + public void setRequestCount(Integer requestCount) { + this.requestCount = (requestCount != null && requestCount > 0) ? requestCount : null; + } + + @JsonProperty("periodInSeconds") + public Integer getPeriodInSeconds() { + return periodInSeconds; + } + + @JsonProperty("periodInSeconds") + public void setPeriodInSeconds(Integer periodInSeconds) { + this.periodInSeconds = (periodInSeconds != null && periodInSeconds > 0) ? periodInSeconds : null; + } + + @JsonProperty("keyExtractors") + public List getKeyExtractors() { + return keyExtractors == null ? Collections.emptyList() : Collections.unmodifiableList(keyExtractors); + } + + @JsonProperty("keyExtractors") + public void setKeyExtractors(List keyExtractors) { + List safe = new ArrayList<>(); + if (keyExtractors != null) { + for (KeyExtractor ke : keyExtractors) { + if (ke != null) safe.add(ke); + } + } + this.keyExtractors = safe; + } + + /** + * Non-throwing helper for validation-style checks in business logic. + */ + public boolean isWellFormed() { + return path != null + && !getMethods().isEmpty() + && requestCount != null + && periodInSeconds != null + && !getKeyExtractors().isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RateLimitRule)) return false; + RateLimitRule that = (RateLimitRule) o; + return Objects.equals(path, that.path) + && Objects.equals(getMethods(), that.getMethods()) + && Objects.equals(requestCount, that.requestCount) + && Objects.equals(periodInSeconds, that.periodInSeconds) + && Objects.equals(getKeyExtractors(), that.getKeyExtractors()); + } + + @Override + public int hashCode() { + return Objects.hash(path, getMethods(), requestCount, periodInSeconds, getKeyExtractors()); + } + + @Override + public String toString() { + return "RateLimitRule{" + + "path='" + path + '\'' + + ", methods=" + getMethods() + + ", requestCount=" + requestCount + + ", periodInSeconds=" + periodInSeconds + + ", keyExtractors=" + getKeyExtractors() + + '}'; + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/CachedBodyHttpServletRequest.java b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/CachedBodyHttpServletRequest.java index 49fa97e1f72..00430ae0ca0 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/CachedBodyHttpServletRequest.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/CachedBodyHttpServletRequest.java @@ -13,14 +13,25 @@ */ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { + public static final int MAX_BODY_SIZE = 1024 * 1024; // 1 MB limit + private final byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); + int contentLength = request.getContentLength(); + if (contentLength > MAX_BODY_SIZE) { + throw new IOException("Request body exceeds maximum allowed size: " + MAX_BODY_SIZE); + } + // Read the entire body and cache it. InputStream is = request.getInputStream(); - this.cachedBody = is.readAllBytes(); + this.cachedBody = is.readNBytes(MAX_BODY_SIZE + 1); + + if (this.cachedBody.length > MAX_BODY_SIZE) { + throw new IOException("Actual body size exceeded 1MB limit."); + } } public byte[] getCachedBody() { diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitContext.java b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitContext.java new file mode 100644 index 00000000000..924b78da2e7 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitContext.java @@ -0,0 +1,40 @@ +package io.jans.as.server.rate; + +import jakarta.servlet.http.HttpServletRequest; + +import java.io.IOException; + +public class RateLimitContext { + + private final HttpServletRequest request; + private final boolean rateLoggingEnabled; + private CachedBodyHttpServletRequest cachedRequest; + + public RateLimitContext(HttpServletRequest request, boolean rateLoggingEnabled) { + this.request = request; + this.rateLoggingEnabled = rateLoggingEnabled; + } + + public HttpServletRequest getRequest() { + return request; + } + + public boolean isRateLoggingEnabled() { + return rateLoggingEnabled; + } + + public boolean isCachedRequestAvailable() { + return cachedRequest != null; + } + + public CachedBodyHttpServletRequest getCachedRequest() throws IOException { + if (cachedRequest == null) { + cachedRequest = new CachedBodyHttpServletRequest(request); + } + return cachedRequest; + } + + public void setCachedRequest(CachedBodyHttpServletRequest cachedRequest) { + this.cachedRequest = cachedRequest; + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitFilter.java b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitFilter.java index eb265eec841..01f35d51df4 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitFilter.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitFilter.java @@ -1,9 +1,6 @@ package io.jans.as.server.rate; -import io.jans.as.client.RegisterRequest; -import io.jans.as.model.common.FeatureFlagType; import io.jans.as.model.config.Constants; -import io.jans.as.model.error.ErrorResponseFactory; import jakarta.annotation.Priority; import jakarta.inject.Inject; import jakarta.servlet.*; @@ -12,14 +9,11 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.Response; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import java.io.IOException; import java.io.PrintWriter; -import java.util.List; /** * @author Yuriy Z @@ -40,8 +34,6 @@ public class RateLimitFilter implements Filter { private Logger log; @Inject private RateLimitService rateLimitService; - @Inject - private ErrorResponseFactory errorResponseFactory; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -49,7 +41,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletResponse httpResponse = (HttpServletResponse) response; try { - httpRequest = validateRateLimit(httpRequest); + httpRequest = rateLimitService.validateRateLimit(httpRequest); chain.doFilter(httpRequest, httpResponse); } catch (RateLimitedException e) { sendTooManyRequestsError(httpResponse); @@ -62,39 +54,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } } - private HttpServletRequest validateRateLimit(HttpServletRequest httpRequest) throws RateLimitedException, IOException { - // if rate_limit flag is disabled immediately return - if (!errorResponseFactory.isFeatureFlagEnabled(FeatureFlagType.RATE_LIMIT)){ - return httpRequest; - } - - final String requestUrl = httpRequest.getRequestURL().toString(); - - boolean isRegisterEndpoint = requestUrl.endsWith("/register"); - - if (isRegisterEndpoint) { - CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpRequest); - final RegisterRequest registerRequest = rateLimitService.parseRegisterRequest(cachedRequest.getCachedBodyAsString()); - String key = "no_key"; - if (registerRequest != null) { - final String ssa = registerRequest.getSoftwareStatement(); - final List redirectUris = registerRequest.getRedirectUris(); - - if (StringUtils.isNotBlank(ssa)) { - // hash ssa to save memory - key = DigestUtils.sha256Hex(ssa); - } else if (CollectionUtils.isNotEmpty(redirectUris) && StringUtils.isNotBlank(redirectUris.get(0))) { - key = redirectUris.get(0); - } - } - - rateLimitService.validateRateLimitForRegister(key); - return cachedRequest; - } - - return httpRequest; - } - private void sendTooManyRequestsError(HttpServletResponse servletResponse) { sendResponse(servletResponse, Response.Status.TOO_MANY_REQUESTS, TOO_MANY_REQUESTS_JSON_ERROR); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitService.java index 410a94ffa04..3c4316c67cf 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/rate/RateLimitService.java @@ -5,14 +5,23 @@ import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.jans.as.client.RegisterRequest; +import io.jans.as.model.common.FeatureFlagType; import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.configuration.rate.RateLimitConfig; +import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.server.register.ws.rs.RegisterService; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.slf4j.Logger; +import java.io.IOException; import java.time.Duration; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -39,6 +48,49 @@ public class RateLimitService { @Inject private RegisterService registerService; + @Inject + private ErrorResponseFactory errorResponseFactory; + + public HttpServletRequest validateRateLimit(HttpServletRequest httpRequest) throws RateLimitedException, IOException { + // if rate_limit flag is disabled immediately return + if (!errorResponseFactory.isFeatureFlagEnabled(FeatureFlagType.RATE_LIMIT)){ + return httpRequest; + } + + RateLimitConfig rateLimitConfiguration = appConfiguration.getRateLimitConfiguration(); + + // no rate limit configuration -> return + if (rateLimitConfiguration == null) { + return httpRequest; + } + + final String requestUrl = httpRequest.getRequestURL().toString(); + + boolean isRegisterEndpoint = requestUrl.endsWith("/register"); + + if (isRegisterEndpoint) { + CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpRequest); + final RegisterRequest registerRequest = parseRegisterRequest(cachedRequest.getCachedBodyAsString()); + String key = "no_key"; + if (registerRequest != null) { + final String ssa = registerRequest.getSoftwareStatement(); + final List redirectUris = registerRequest.getRedirectUris(); + + if (StringUtils.isNotBlank(ssa)) { + // hash ssa to save memory + key = DigestUtils.sha256Hex(ssa); + } else if (CollectionUtils.isNotEmpty(redirectUris) && StringUtils.isNotBlank(redirectUris.get(0))) { + key = redirectUris.get(0); + } + } + + validateRateLimitForRegister(key); + return cachedRequest; + } + + return httpRequest; + } + public void validateRateLimitForRegister(String key) throws RateLimitedException { int requestLimit = getRequestLimit(appConfiguration.getRateLimitRegistrationRequestCount()); int periodLimit = getPeriodLimit(appConfiguration.getRateLimitRegistrationPeriodInSeconds()); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/rate/RateLimitServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/rate/RateLimitServiceTest.java index ceff34aeb88..a2782d1ca22 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/rate/RateLimitServiceTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/rate/RateLimitServiceTest.java @@ -1,6 +1,7 @@ package io.jans.as.server.rate; import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; import io.jans.as.server.register.ws.rs.RegisterService; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -32,6 +33,9 @@ public class RateLimitServiceTest { @Mock private RegisterService registerService; + @Mock + private ErrorResponseFactory errorResponseFactory; + @Test public void validateRateLimitForRegister_forSingleCall_shouldPassSuccessfully() throws RateLimitedException { rateLimitService.validateRateLimitForRegister("some_ssa"); diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/rate/TestServletInputStream.java b/jans-auth-server/server/src/test/java/io/jans/as/server/rate/TestServletInputStream.java new file mode 100644 index 00000000000..1846f531f86 --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/rate/TestServletInputStream.java @@ -0,0 +1,40 @@ +package io.jans.as.server.rate; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +public class TestServletInputStream extends ServletInputStream { + + private final ByteArrayInputStream bodyInputStream; + + public TestServletInputStream(String body) { + this(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + } + + public TestServletInputStream(ByteArrayInputStream bodyInputStream) { + this.bodyInputStream = bodyInputStream; + } + + @Override + public int read() { + return bodyInputStream.read(); + } + + @Override + public boolean isFinished() { + return bodyInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + +}