Skip to content

Commit b919980

Browse files
committed
Merge remote-tracking branch 'origin/master' into authorization-feature-and-bearer
2 parents f4f791f + 846796b commit b919980

File tree

10 files changed

+378
-371
lines changed

10 files changed

+378
-371
lines changed

plume-framework-dependencies/pom.xml

Lines changed: 289 additions & 289 deletions
Large diffs are not rendered by default.

plume-web-jersey/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ Content size limit
3131
------------------
3232
In order to protect the backend against attack that would send huge content, it is possible to limit the size of the content that can be sent to the backend.
3333

34-
To do so, register the `ContentControlFeature` in Jersey: `resourceConfig.register(ContentControlFeature.class);`
35-
By default the content size of body is limited to 500 KB. This limit can be override for the whole api by using the `ContentControlFeatureFactory` to specify your own limit.
34+
To do so, register the `ContentSizeLimitFeature` in Jersey: `resourceConfig.register(ContentSizeLimitFeature.class);`
35+
By default the content size of body is limited to 500 KB. This limit can be overridden for the whole api by using the `ContentSizeLimitFeatureFactory` to specify your own limit.
3636

3737
Usage example:
3838
```java
3939
resourceConfig.register(new AbstractBinder() {
4040
@Override
4141
protected void configure() {
42-
bindFactory(new ContentControlFeatureFactory(1000 * 1024 /* 1MB */)).to(ContentControlFeature.class);
42+
bindFactory(new ContentSizeLimitFeatureFactory(1000 * 1024 /* 1MB */)).to(ContentSizeLimitFeature.class);
4343
}
4444
});
4545
```

plume-web-jersey/src/main/java/com/coreoz/plume/jersey/errors/WsError.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public interface WsError {
1212
WsError FIELD_REQUIRED = new WsErrorInternal("FIELD_REQUIRED");
1313
WsError EMAIL_INVALID = new WsErrorInternal("EMAIL_INVALID");
1414
WsError COLOR_INVALID = new WsErrorInternal("COLOR_INVALID");
15+
WsError CONTENT_SIZE_LIMIT_EXCEEDED = new WsErrorInternal("CONTENT_SIZE_LIMIT_EXCEEDED");
1516

1617
/**
1718
* Returns the name of the error

plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/control/ContentControlFeatureFactory.java

Lines changed: 0 additions & 21 deletions
This file was deleted.

plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/control/ContentSizeLimit.java renamed to plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/size/ContentSizeLimit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.coreoz.plume.jersey.security.control;
1+
package com.coreoz.plume.jersey.security.size;
22

33
import static java.lang.annotation.ElementType.METHOD;
44
import static java.lang.annotation.ElementType.TYPE;

plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/control/ContentControlFeature.java renamed to plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/size/ContentSizeLimitFeature.java

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
1-
package com.coreoz.plume.jersey.security.control;
1+
package com.coreoz.plume.jersey.security.size;
22

3-
import java.io.IOException;
4-
import java.io.InputStream;
5-
import java.lang.reflect.AnnotatedElement;
6-
7-
import jakarta.ws.rs.WebApplicationException;
3+
import com.coreoz.plume.jersey.errors.WsError;
4+
import jakarta.ws.rs.ClientErrorException;
5+
import jakarta.ws.rs.container.ContainerRequestContext;
6+
import jakarta.ws.rs.container.ContainerRequestFilter;
87
import jakarta.ws.rs.container.DynamicFeature;
98
import jakarta.ws.rs.container.ResourceInfo;
109
import jakarta.ws.rs.core.FeatureContext;
1110
import jakarta.ws.rs.core.HttpHeaders;
11+
import jakarta.ws.rs.core.MediaType;
1212
import jakarta.ws.rs.core.Response;
13-
import jakarta.ws.rs.ext.ReaderInterceptor;
14-
import jakarta.ws.rs.ext.ReaderInterceptorContext;
1513
import lombok.extern.slf4j.Slf4j;
1614

17-
@Slf4j
18-
public class ContentControlFeature implements DynamicFeature {
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.lang.reflect.AnnotatedElement;
1918

19+
@Slf4j
20+
public class ContentSizeLimitFeature implements DynamicFeature {
2021
public static final int DEFAULT_MAX_SIZE = 500 * 1024; // 500 KB
22+
// We use a string response directly because Jersey does not accept an objet here (it would return a 500 error)
23+
static final String JSON_ENTITY_TOO_LARGE_ERROR = "{\"errorCode\":\""+WsError.CONTENT_SIZE_LIMIT_EXCEEDED.name()+"\",\"statusArguments\":[]}";
2124
private final Integer maxSize;
2225

23-
public ContentControlFeature(int maxSize) {
26+
public ContentSizeLimitFeature(int maxSize) {
2427
this.maxSize = maxSize;
2528
}
2629

27-
public ContentControlFeature() {
30+
public ContentSizeLimitFeature() {
2831
this.maxSize = DEFAULT_MAX_SIZE;
2932
}
3033

3134
public Integer getContentSizeLimit() {
32-
if (maxSize == null) {
33-
return DEFAULT_MAX_SIZE;
34-
}
3535
return maxSize;
3636
}
3737

@@ -40,15 +40,14 @@ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
4040
addContentSizeFilter(resourceInfo.getResourceMethod(), context);
4141
}
4242

43-
private void addContentSizeFilter(AnnotatedElement annotatedElement, FeatureContext methodResourcecontext) {
43+
private void addContentSizeFilter(AnnotatedElement annotatedElement, FeatureContext methodResourceContext) {
4444
ContentSizeLimit contentSizeLimit = annotatedElement.getAnnotation(ContentSizeLimit.class);
45-
methodResourcecontext.register(new ContentSizeLimitInterceptor(
46-
contentSizeLimit != null ? contentSizeLimit.value() : maxSize
47-
));
45+
methodResourceContext.register(new ContentSizeLimitInterceptor(
46+
contentSizeLimit != null ? contentSizeLimit.value() : maxSize
47+
));
4848
}
4949

50-
public static class ContentSizeLimitInterceptor implements ReaderInterceptor {
51-
50+
public static class ContentSizeLimitInterceptor implements ContainerRequestFilter {
5251
private final int maxSize;
5352

5453
public ContentSizeLimitInterceptor(int maxSize) {
@@ -57,7 +56,7 @@ public ContentSizeLimitInterceptor(int maxSize) {
5756

5857
// https://stackoverflow.com/questions/24516444/best-way-to-make-jersey-2-x-refuse-requests-with-incorrect-content-length
5958
@Override
60-
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException {
59+
public void filter(ContainerRequestContext context) {
6160
int headerContentLength = maxSize; // default value for GET or chunked body
6261
String contentLengthHeader = context.getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH);
6362
if (contentLengthHeader != null) {
@@ -69,17 +68,12 @@ public Object aroundReadFrom(ReaderInterceptorContext context) throws IOExceptio
6968
}
7069

7170
if (headerContentLength > maxSize) {
72-
throw new WebApplicationException(
73-
Response.status(Response.Status.REQUEST_ENTITY_TOO_LARGE)
74-
.entity("Content size limit exceeded.")
75-
.build()
76-
);
71+
logger.warn("Client tried to send a request body that is too large: max allowed size={}, client request body size={}", maxSize, headerContentLength);
72+
throw makeEntityTooLargeException();
7773
}
7874

79-
final InputStream contextInputStream = context.getInputStream();
80-
context.setInputStream(new SizeLimitingInputStream(contextInputStream, headerContentLength));
81-
82-
return context.proceed();
75+
final InputStream contextInputStream = context.getEntityStream();
76+
context.setEntityStream(new SizeLimitingInputStream(contextInputStream, headerContentLength));
8377
}
8478

8579
public static final class SizeLimitingInputStream extends InputStream {
@@ -119,7 +113,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
119113
@Override
120114
public long skip(final long n) throws IOException {
121115
final long skip = delegateInputStream.skip(n);
122-
readAndCheck(skip != -1 ? skip : 0);
116+
readAndCheck(skip);
123117
return skip;
124118
}
125119

@@ -160,13 +154,20 @@ private void readAndCheck(final long read) {
160154
} catch (IOException e) {
161155
logger.error("Error while closing the input stream", e);
162156
}
163-
throw new WebApplicationException(
164-
Response.status(Response.Status.REQUEST_ENTITY_TOO_LARGE)
165-
.entity("Content size limit exceeded.")
166-
.build()
167-
);
157+
logger.warn("Client tried to send a request body that is too large while sending a content-length headers that fits the max allowed request body size");
158+
throw makeEntityTooLargeException();
168159
}
169160
}
170161
}
162+
163+
private static RuntimeException makeEntityTooLargeException() {
164+
return new ClientErrorException(Response
165+
.status(Response.Status.REQUEST_ENTITY_TOO_LARGE)
166+
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
167+
// We use a string response directly because Jersey does not accept an objet here (it would return a 500 error)
168+
.entity(JSON_ENTITY_TOO_LARGE_ERROR)
169+
.build()
170+
);
171+
}
171172
}
172173
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.coreoz.plume.jersey.security.size;
2+
3+
import org.glassfish.hk2.api.Factory;
4+
5+
public class ContentSizeLimitFeatureFactory implements Factory<ContentSizeLimitFeature> {
6+
private final Integer maxSize;
7+
8+
public ContentSizeLimitFeatureFactory(int maxSize) {
9+
this.maxSize = maxSize;
10+
}
11+
12+
@Override
13+
public ContentSizeLimitFeature provide() {
14+
return new ContentSizeLimitFeature(maxSize);
15+
}
16+
17+
@Override
18+
public void dispose(ContentSizeLimitFeature instance) {
19+
// unused
20+
}
21+
}

plume-web-jersey/src/test/java/com/coreoz/plume/jersey/control/ContentSizeLimitTest.java renamed to plume-web-jersey/src/test/java/com/coreoz/plume/jersey/security/size/ContentSizeLimitTest.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
package com.coreoz.plume.jersey.control;
2-
3-
import org.junit.Test;
1+
package com.coreoz.plume.jersey.security.size;
42

3+
import com.coreoz.plume.jersey.errors.WsError;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
56
import jakarta.ws.rs.client.Entity;
7+
import jakarta.ws.rs.client.Invocation.Builder;
68
import jakarta.ws.rs.core.Application;
79
import jakarta.ws.rs.core.HttpHeaders;
810
import jakarta.ws.rs.core.MediaType;
911
import jakarta.ws.rs.core.Response;
10-
import jakarta.ws.rs.client.Invocation.Builder;
11-
12+
import lombok.SneakyThrows;
13+
import org.assertj.core.api.Assertions;
1214
import org.glassfish.jersey.server.ResourceConfig;
1315
import org.glassfish.jersey.test.JerseyTest;
16+
import org.junit.Test;
1417

1518
import static org.junit.Assert.assertEquals;
1619

17-
import com.coreoz.plume.jersey.security.control.ContentControlFeature;
18-
import com.coreoz.plume.jersey.security.control.ContentControlFeatureFactory;
19-
2020
public class ContentSizeLimitTest extends JerseyTest {
2121

2222
@Override
2323
protected Application configure() {
2424
ResourceConfig config = new ResourceConfig(TestContentSizeResource.class);
25-
config.register(ContentControlFeature.class);
25+
config.register(ContentSizeLimitFeature.class);
2626
return config;
2727
}
2828

@@ -33,19 +33,26 @@ public void checkContentSize_withBody_whenWithinDefaultLimit_shouldReturn200() {
3333
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
3434
}
3535

36+
@SneakyThrows
3637
@Test
3738
public void checkContentSize_withBody_whenBeyondDefaultLimit_shouldReturn413() {
3839
// Generate a byte array of ContentControlFeature.DEFAULT_MAX_SIZE + 1
39-
byte[] data = new byte[ContentControlFeature.DEFAULT_MAX_SIZE + 1];
40+
byte[] data = new byte[ContentSizeLimitFeature.DEFAULT_MAX_SIZE + 1];
4041
Builder request = target("/test/upload-default").request();
4142
Entity<byte[]> entity = Entity.entity(data, MediaType.APPLICATION_OCTET_STREAM);
42-
assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), request.post(entity).getStatus());
43+
Response response = request.post(entity);
44+
assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), response.getStatus());
45+
// make sure that a valid json error response is returned
46+
assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getMediaType());
47+
JsonNode responseBody = new ObjectMapper().readTree(response.readEntity(String.class));
48+
Assertions.assertThat(responseBody).isNotNull();
49+
Assertions.assertThat(responseBody.get("errorCode").asText()).isEqualTo(WsError.CONTENT_SIZE_LIMIT_EXCEEDED.name());
4350
}
4451

4552
@Test
4653
public void checkContentSize_withBody_whenContentLengthIsWrong_shouldReturn413() {
4754
// Generate a byte array of ContentControlFeature.DEFAULT_MAX_SIZE + 1
48-
byte[] data = new byte[ContentControlFeature.DEFAULT_MAX_SIZE + 1];
55+
byte[] data = new byte[ContentSizeLimitFeature.DEFAULT_MAX_SIZE + 1];
4956
Builder request = target("/test/upload-default").request();
5057
request.header(HttpHeaders.CONTENT_LENGTH, null);
5158
assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), request.post(Entity.entity(data, MediaType.APPLICATION_OCTET_STREAM)).getStatus());
@@ -83,8 +90,8 @@ public void checkContentSize_withoutBody_whenCustomLimit_shouldReturn200() {
8390
public void checkMaxSize_whenCustomControlFeature_shouldSuccess() {
8491
// Custom max size
8592
Integer customMaxSize = 300;
86-
ContentControlFeatureFactory contentControlFeatureFactory = new ContentControlFeatureFactory(customMaxSize);
87-
ContentControlFeature contentControlFeature = contentControlFeatureFactory.provide();
88-
assertEquals(customMaxSize, contentControlFeature.getContentSizeLimit());
93+
ContentSizeLimitFeatureFactory contentControlFeatureFactory = new ContentSizeLimitFeatureFactory(customMaxSize);
94+
ContentSizeLimitFeature contentSizeLimitFeature = contentControlFeatureFactory.provide();
95+
assertEquals(customMaxSize, contentSizeLimitFeature.getContentSizeLimit());
8996
}
9097
}

plume-web-jersey/src/test/java/com/coreoz/plume/jersey/control/SizeLimitingInputStreamTest.java renamed to plume-web-jersey/src/test/java/com/coreoz/plume/jersey/security/size/SizeLimitingInputStreamTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.coreoz.plume.jersey.control;
1+
package com.coreoz.plume.jersey.security.size;
22

33
import org.junit.Test;
44
import org.junit.After;
@@ -13,7 +13,7 @@
1313

1414
import java.io.ByteArrayInputStream;
1515

16-
import com.coreoz.plume.jersey.security.control.ContentControlFeature.ContentSizeLimitInterceptor.SizeLimitingInputStream;
16+
import com.coreoz.plume.jersey.security.size.ContentSizeLimitFeature.ContentSizeLimitInterceptor.SizeLimitingInputStream;
1717

1818
public class SizeLimitingInputStreamTest {
1919

plume-web-jersey/src/test/java/com/coreoz/plume/jersey/control/TestContentSizeResource.java renamed to plume-web-jersey/src/test/java/com/coreoz/plume/jersey/security/size/TestContentSizeResource.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
package com.coreoz.plume.jersey.control;
2-
3-
import com.coreoz.plume.jersey.security.control.ContentSizeLimit;
1+
package com.coreoz.plume.jersey.security.size;
42

53
import jakarta.ws.rs.GET;
64
import jakarta.ws.rs.POST;

0 commit comments

Comments
 (0)