Skip to content

Commit

Permalink
SIVA-583 Implement file size limitations for post requests
Browse files Browse the repository at this point in the history
  • Loading branch information
ivoMattus committed Feb 1, 2024
1 parent eab2ec3 commit 4eade16
Show file tree
Hide file tree
Showing 17 changed files with 1,267 additions and 1 deletion.
8 changes: 8 additions & 0 deletions siva-parent/siva-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@
<artifactId>json</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>ee.openid.siva</groupId>
<artifactId>validation-commons</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import ee.openeid.siva.validation.exception.MalformedSignatureFileException;
import ee.openeid.siva.validation.exception.ValidationServiceException;
import ee.openeid.siva.validation.service.signature.policy.InvalidPolicyException;
import ee.openeid.siva.webapp.request.limitation.RequestSizeLimitExceededException;
import ee.openeid.siva.webapp.response.erroneus.RequestValidationError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
Expand Down Expand Up @@ -85,6 +86,14 @@ public RequestValidationError handleInvalidPolicyException(InvalidPolicyExceptio
return requestValidationError;
}

@ExceptionHandler(RequestSizeLimitExceededException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public RequestValidationError handleRequestSizeLimitExceededException(RequestSizeLimitExceededException e) {
RequestValidationError requestValidationError = new RequestValidationError();
requestValidationError.addFieldError("request", e.getMessage());
return requestValidationError;
}


private String getMessage(String key) {
return messageSource.getMessage(key, null, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.configuration;

import ee.openeid.siva.webapp.request.validation.annotations.DataSizeMin;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.validation.annotation.Validated;

@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = "siva.http.request")
public class HttpRequestLimitConfigurationProperties {
@DataSizeMin(1L)
private DataSize maxRequestSizeLimit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.limitation;

import ee.openeid.siva.webapp.configuration.HttpRequestLimitConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
@ConditionalOnProperty(prefix = "siva.http.request", name = "max-request-size-limit")
public class ApplicationRequestSizeLimitFilter extends OncePerRequestFilter {

private final long maxRequestSizeLimit;

public ApplicationRequestSizeLimitFilter(HttpRequestLimitConfigurationProperties requestLimitProperties) {
maxRequestSizeLimit = requestLimitProperties.getMaxRequestSizeLimit().toBytes();
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
SizeLimitingHttpServletRequest sizeLimitingHttpServletRequest = new SizeLimitingHttpServletRequest(request, maxRequestSizeLimit);
filterChain.doFilter(sizeLimitingHttpServletRequest, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.limitation;

public class RequestSizeLimitExceededException extends RuntimeException {

public RequestSizeLimitExceededException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.limitation;

import lombok.Getter;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;

@Getter
public class SizeLimitingHttpServletRequest extends HttpServletRequestWrapper {
private final long maximumAllowedReadLimit;

public SizeLimitingHttpServletRequest(HttpServletRequest request, long maximumAllowedReadLimit) {
super(request);
this.maximumAllowedReadLimit = maximumAllowedReadLimit;
}

@Override
public ServletInputStream getInputStream() throws IOException {
ensureContentLengthDoesNotExceedLimit();

return new SizeLimitingServletInputStream(super.getInputStream(), maximumAllowedReadLimit);
}

private void ensureContentLengthDoesNotExceedLimit() {
long contentLength = getContentLengthLong();

if (contentLength > maximumAllowedReadLimit) {
throw new RequestSizeLimitExceededException(String.format(
"Request content-length (%d bytes) exceeds request size limit (%d bytes)",
contentLength, maximumAllowedReadLimit
));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.limitation;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;

@RequiredArgsConstructor
public class SizeLimitingServletInputStream extends ServletInputStream {

private final @NonNull ServletInputStream servletInputStream;
private final long maximumAllowedReadLimit;

private long bytesRead;

@Override
public int available() throws IOException {
return servletInputStream.available();
}

@Override
public void close() throws IOException {
servletInputStream.close();
}

@Override
public boolean isFinished() {
return servletInputStream.isFinished();
}

@Override
public boolean isReady() {
return servletInputStream.isReady();
}

@Override
public synchronized void mark(int readlimit) {
throw new UnsupportedOperationException("Mark not supported");
}

@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException("Setting read listener not supported");
}

@Override
public int read() throws IOException {
final int result = servletInputStream.read();

if (result >= 0) {
++bytesRead;
ensureLimitNotExceeded();
}

return result;
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
final int result = servletInputStream.read(b, off, calculateMaximumBytesToRead(len));

if (result >= 0) {
bytesRead += result;
ensureLimitNotExceeded();
}

return result;
}

@Override
public synchronized void reset() {
throw new UnsupportedOperationException("Mark not supported");
}

private int calculateMaximumBytesToRead(int requestedBytesToRead) {
final long remainingAllowedReadLength = maximumAllowedReadLimit - bytesRead;
return remainingAllowedReadLength < requestedBytesToRead
? (int) (remainingAllowedReadLength + 1)
: requestedBytesToRead;
}

private void ensureLimitNotExceeded() {
if (bytesRead > maximumAllowedReadLimit) {
throw new RequestSizeLimitExceededException(String.format(
"Request body length exceeds request size limit (%d bytes)",
maximumAllowedReadLimit
));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.validation.annotations;

import ee.openeid.siva.webapp.request.validation.validators.DataSizeMinValidator;
import org.springframework.util.unit.DataUnit;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DataSizeMinValidator.class)
public @interface DataSizeMin {

String message() default "must not be less than {value} {unit}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
long value() default 0L;
DataUnit unit() default DataUnit.BYTES;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 Riigi Infosüsteemi Amet
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and limitations under the Licence.
*/

package ee.openeid.siva.webapp.request.validation.validators;

import ee.openeid.siva.webapp.request.validation.annotations.DataSizeMin;
import org.springframework.util.unit.DataSize;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DataSizeMinValidator implements ConstraintValidator<DataSizeMin, DataSize> {

private DataSize minAllowedDataSize;

@Override
public void initialize(DataSizeMin constraintAnnotation) {
minAllowedDataSize = DataSize.of(constraintAnnotation.value(), constraintAnnotation.unit());
}

@Override
public boolean isValid(DataSize dataSize, ConstraintValidatorContext constraintValidatorContext) {
return (dataSize == null) || dataSize.compareTo(minAllowedDataSize) >= 0;
}

}
Loading

0 comments on commit 4eade16

Please sign in to comment.