Skip to content

Commit 90ea6dd

Browse files
authored
Refactor/avoid user controlled input (#381)
* refactor(plugin): avoid user controlled input * refactor(plugin): avoid user controlled input * chore: fixes after rebase * feat(core): add schema name to AsyncHeaders * test(kafka): fix SpringwolfKafkaControllerIntegrationTest
1 parent adc1d80 commit 90ea6dd

File tree

13 files changed

+164
-92
lines changed

13 files changed

+164
-92
lines changed

springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public String register(AsyncHeaders headers) {
5858
log.debug("Registering schema for {}", headers.getSchemaName());
5959

6060
MapSchema headerSchema = new MapSchema();
61+
headerSchema.setName(headers.getSchemaName());
6162
headerSchema.properties(headers);
6263

6364
this.definitions.put(headers.getSchemaName(), headerSchema);

springwolf-plugins/springwolf-amqp-plugin/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ dependencies {
2424
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
2525
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
2626

27+
implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}"
28+
2729
compileOnly "com.google.code.findbugs:jsr305:${jsr305Version}"
2830
permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}"
2931

springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/amqp/SpringwolfAmqpProducerConfiguration.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import io.github.stavshamir.springwolf.asyncapi.AsyncApiService;
66
import io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfAmqpController;
7-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
87
import io.github.stavshamir.springwolf.producer.SpringwolfAmqpProducer;
8+
import io.github.stavshamir.springwolf.schemas.SchemasService;
99
import org.springframework.amqp.rabbit.core.RabbitTemplate;
1010
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1111
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -37,9 +37,7 @@ public SpringwolfAmqpProducer springwolfAmqpProducer(
3737
@Bean
3838
@ConditionalOnMissingBean
3939
public SpringwolfAmqpController springwolfAmqpController(
40-
AsyncApiDocketService asyncApiDocketService,
41-
SpringwolfAmqpProducer springwolfAmqpProducer,
42-
ObjectMapper objectMapper) {
43-
return new SpringwolfAmqpController(asyncApiDocketService, springwolfAmqpProducer, objectMapper);
40+
SchemasService schemasService, SpringwolfAmqpProducer springwolfAmqpProducer, ObjectMapper objectMapper) {
41+
return new SpringwolfAmqpController(schemasService, springwolfAmqpProducer, objectMapper);
4442
}
4543
}

springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfAmqpController.java

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import io.github.stavshamir.springwolf.asyncapi.controller.dtos.MessageDto;
7-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
87
import io.github.stavshamir.springwolf.producer.SpringwolfAmqpProducer;
8+
import io.github.stavshamir.springwolf.schemas.SchemasService;
9+
import io.swagger.v3.oas.models.media.Schema;
910
import lombok.RequiredArgsConstructor;
1011
import lombok.extern.slf4j.Slf4j;
1112
import org.springframework.beans.factory.InitializingBean;
@@ -25,7 +26,7 @@
2526
@RequiredArgsConstructor
2627
public class SpringwolfAmqpController implements InitializingBean {
2728

28-
private final AsyncApiDocketService asyncApiDocketService;
29+
private final SchemasService schemasService;
2930

3031
private final SpringwolfAmqpProducer producer;
3132

@@ -38,22 +39,37 @@ public void publish(@RequestParam String topic, @RequestBody MessageDto message)
3839
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "AMQP producer is not enabled");
3940
}
4041

41-
String payloadType = message.getPayloadType();
42-
if (payloadType.startsWith(asyncApiDocketService.getAsyncApiDocket().getBasePackage())) {
43-
try {
44-
Class<?> payloadClass = Class.forName(payloadType);
45-
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
42+
boolean foundDefinition = false;
43+
String messagePayloadType = message.getPayloadType();
44+
for (Schema<?> value : schemasService.getDefinitions().values()) {
45+
String schemaPayloadType = value.getName();
46+
// security: match against user input, but always use our controlled data from the DefaultSchemaService
47+
if (schemaPayloadType.equals(messagePayloadType)) {
48+
publishMessage(topic, message, schemaPayloadType);
4649

47-
log.debug("Publishing to amqp queue {}: {}", topic, message.getPayload());
48-
producer.send(topic, payload);
49-
} catch (ClassNotFoundException | JsonProcessingException ex) {
50-
throw new ResponseStatusException(
51-
HttpStatus.BAD_REQUEST,
52-
MessageFormat.format(
53-
"Unable to create payload {0} from data: {1}", payloadType, message.getPayload()));
50+
foundDefinition = true;
51+
break;
5452
}
55-
} else {
56-
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No payloadType specified.");
53+
}
54+
55+
if (!foundDefinition) {
56+
throw new ResponseStatusException(
57+
HttpStatus.BAD_REQUEST, "Specified payloadType is not a registered springwolf schema.");
58+
}
59+
}
60+
61+
private void publishMessage(String topic, MessageDto message, String schemaPayloadType) {
62+
try {
63+
Class<?> payloadClass = Class.forName(schemaPayloadType);
64+
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
65+
66+
log.debug("Publishing to amqp queue {}: {}", topic, message.getPayload());
67+
producer.send(topic, payload);
68+
} catch (ClassNotFoundException | JsonProcessingException ex) {
69+
throw new ResponseStatusException(
70+
HttpStatus.BAD_REQUEST,
71+
MessageFormat.format(
72+
"Unable to create payload {0} from data: {1}", schemaPayloadType, message.getPayload()));
5773
}
5874
}
5975

springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfKafkaController.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import io.github.stavshamir.springwolf.asyncapi.controller.dtos.MessageDto;
7-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
87
import io.github.stavshamir.springwolf.producer.SpringwolfKafkaProducer;
8+
import io.github.stavshamir.springwolf.schemas.SchemasService;
9+
import io.swagger.v3.oas.models.media.Schema;
910
import lombok.RequiredArgsConstructor;
1011
import lombok.extern.slf4j.Slf4j;
1112
import org.springframework.beans.factory.InitializingBean;
@@ -25,7 +26,7 @@
2526
@RequiredArgsConstructor
2627
public class SpringwolfKafkaController implements InitializingBean {
2728

28-
private final AsyncApiDocketService asyncApiDocketService;
29+
private final SchemasService schemasService;
2930

3031
private final SpringwolfKafkaProducer producer;
3132

@@ -34,28 +35,43 @@ public class SpringwolfKafkaController implements InitializingBean {
3435
@PostMapping("/publish")
3536
public void publish(@RequestParam String topic, @RequestBody MessageDto message) {
3637
if (!producer.isEnabled()) {
37-
log.debug("Kafka producer is not enabled - message will not be published");
38+
log.warn("Kafka producer is not enabled - message will not be published");
3839
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Kafka producer is not enabled");
3940
}
4041

41-
String payloadType = message.getPayloadType();
42-
if (payloadType.startsWith(asyncApiDocketService.getAsyncApiDocket().getBasePackage())) {
43-
try {
44-
Class<?> payloadClass = Class.forName(payloadType);
45-
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
42+
boolean foundDefinition = false;
43+
String messagePayloadType = message.getPayloadType();
44+
for (Schema<?> value : schemasService.getDefinitions().values()) {
45+
String schemaPayloadType = value.getName();
46+
// security: match against user input, but always use our controlled data from the DefaultSchemaService
47+
if (schemaPayloadType.equals(messagePayloadType)) {
48+
publishMessage(topic, message, schemaPayloadType);
4649

47-
String kafkaKey =
48-
message.getBindings() != null ? message.getBindings().get("key") : null;
49-
log.debug("Publishing to kafka topic {} with key {}: {}", topic, kafkaKey, message);
50-
producer.send(topic, kafkaKey, message.getHeaders(), payload);
51-
} catch (ClassNotFoundException | JsonProcessingException ex) {
52-
throw new ResponseStatusException(
53-
HttpStatus.BAD_REQUEST,
54-
MessageFormat.format(
55-
"Unable to create payload {0} from data: {1}", payloadType, message.getPayload()));
50+
foundDefinition = true;
51+
break;
5652
}
57-
} else {
58-
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No payloadType specified.");
53+
}
54+
55+
if (!foundDefinition) {
56+
throw new ResponseStatusException(
57+
HttpStatus.BAD_REQUEST, "Specified payloadType is not a registered springwolf schema.");
58+
}
59+
}
60+
61+
private void publishMessage(String topic, MessageDto message, String schemaPayloadType) {
62+
try {
63+
Class<?> payloadClass = Class.forName(schemaPayloadType);
64+
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
65+
66+
String kafkaKey =
67+
message.getBindings() != null ? message.getBindings().get("key") : null;
68+
log.debug("Publishing to kafka topic {} with key {}: {}", topic, kafkaKey, message);
69+
producer.send(topic, kafkaKey, message.getHeaders(), payload);
70+
} catch (ClassNotFoundException | JsonProcessingException ex) {
71+
throw new ResponseStatusException(
72+
HttpStatus.BAD_REQUEST,
73+
MessageFormat.format(
74+
"Unable to create payload {0} from data: {1}", schemaPayloadType, message.getPayload()));
5975
}
6076
}
6177

springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/kafka/SpringwolfKafkaProducerConfiguration.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaController;
6-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
76
import io.github.stavshamir.springwolf.configuration.properties.SpringwolfKafkaConfigProperties;
87
import io.github.stavshamir.springwolf.producer.SpringwolfKafkaProducer;
98
import io.github.stavshamir.springwolf.producer.SpringwolfKafkaTemplateFactory;
9+
import io.github.stavshamir.springwolf.schemas.SchemasService;
1010
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1111
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1212
import org.springframework.context.annotation.Bean;
@@ -25,10 +25,8 @@ public class SpringwolfKafkaProducerConfiguration {
2525
@Bean
2626
@ConditionalOnMissingBean
2727
public SpringwolfKafkaController springwolfKafkaController(
28-
AsyncApiDocketService asyncApiDocketService,
29-
SpringwolfKafkaProducer springwolfKafkaProducer,
30-
ObjectMapper objectMapper) {
31-
return new SpringwolfKafkaController(asyncApiDocketService, springwolfKafkaProducer, objectMapper);
28+
SchemasService schemasService, SpringwolfKafkaProducer springwolfKafkaProducer, ObjectMapper objectMapper) {
29+
return new SpringwolfKafkaController(schemasService, springwolfKafkaProducer, objectMapper);
3230
}
3331

3432
@Bean

springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/SpringwolfKafkaControllerIntegrationTest.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
import com.fasterxml.jackson.annotation.JsonProperty;
55
import io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaController;
6-
import io.github.stavshamir.springwolf.configuration.DefaultAsyncApiDocketService;
76
import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties;
87
import io.github.stavshamir.springwolf.producer.SpringwolfKafkaProducer;
8+
import io.github.stavshamir.springwolf.schemas.DefaultSchemasService;
9+
import io.github.stavshamir.springwolf.schemas.SchemasService;
10+
import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator;
911
import lombok.AllArgsConstructor;
1012
import lombok.Builder;
1113
import lombok.Data;
@@ -39,7 +41,8 @@
3941
classes = {
4042
SpringwolfKafkaController.class,
4143
SpringwolfKafkaProducer.class,
42-
DefaultAsyncApiDocketService.class,
44+
DefaultSchemasService.class,
45+
ExampleJsonGenerator.class,
4346
SpringwolfConfigProperties.class,
4447
})
4548
@TestPropertySource(
@@ -49,13 +52,17 @@
4952
"springwolf.docket.info.version=1.0",
5053
"springwolf.docket.servers.kafka.protocol=kafka",
5154
"springwolf.docket.servers.kafka.url=127.0.0.1",
52-
"springwolf.plugin.kafka.publishing.enabled=true"
55+
"springwolf.plugin.kafka.publishing.enabled=true",
56+
"springwolf.use-fqn=true"
5357
})
5458
class SpringwolfKafkaControllerIntegrationTest {
5559

5660
@Autowired
5761
private MockMvc mvc;
5862

63+
@Autowired
64+
private SchemasService schemasService;
65+
5966
@MockBean
6067
private SpringwolfKafkaProducer springwolfKafkaProducer;
6168

@@ -68,6 +75,8 @@ class SpringwolfKafkaControllerIntegrationTest {
6875
@BeforeEach
6976
void setup() {
7077
when(springwolfKafkaProducer.isEnabled()).thenReturn(true);
78+
79+
schemasService.register(PayloadDto.class);
7180
}
7281

7382
@Test

springwolf-plugins/springwolf-sns-plugin/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ dependencies {
3232
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
3333
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
3434

35+
implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}"
36+
3537
compileOnly "com.google.code.findbugs:jsr305:${jsr305Version}"
3638
permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}"
3739

springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfSnsController.java

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import io.github.stavshamir.springwolf.asyncapi.controller.dtos.MessageDto;
7-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
87
import io.github.stavshamir.springwolf.producer.SpringwolfSnsProducer;
8+
import io.github.stavshamir.springwolf.schemas.SchemasService;
9+
import io.swagger.v3.oas.models.media.Schema;
910
import lombok.RequiredArgsConstructor;
1011
import lombok.extern.slf4j.Slf4j;
1112
import org.springframework.beans.factory.InitializingBean;
@@ -26,7 +27,7 @@
2627
@RequiredArgsConstructor
2728
public class SpringwolfSnsController implements InitializingBean {
2829

29-
private final AsyncApiDocketService asyncApiDocketService;
30+
private final SchemasService schemasService;
3031

3132
private final SpringwolfSnsProducer producer;
3233

@@ -39,22 +40,37 @@ public void publish(@RequestParam String topic, @RequestBody MessageDto message)
3940
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "SNS producer is not enabled");
4041
}
4142

42-
String payloadType = message.getPayloadType();
43-
if (payloadType.startsWith(asyncApiDocketService.getAsyncApiDocket().getBasePackage())) {
44-
try {
45-
Class<?> payloadClass = Class.forName(payloadType);
46-
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
43+
boolean foundDefinition = false;
44+
String messagePayloadType = message.getPayloadType();
45+
for (Schema<?> value : schemasService.getDefinitions().values()) {
46+
String schemaPayloadType = value.getName();
47+
// security: match against user input, but always use our controlled data from the DefaultSchemaService
48+
if (schemaPayloadType.equals(messagePayloadType)) {
49+
publishMessage(topic, message, schemaPayloadType);
4750

48-
log.debug("Publishing to sns queue {}: {}", topic, payload);
49-
producer.send(topic, MessageBuilder.withPayload(payload).build());
50-
} catch (ClassNotFoundException | JsonProcessingException ex) {
51-
throw new ResponseStatusException(
52-
HttpStatus.BAD_REQUEST,
53-
MessageFormat.format(
54-
"Unable to create payload {0} from data: {1}", payloadType, message.getPayload()));
51+
foundDefinition = true;
52+
break;
5553
}
56-
} else {
57-
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No payloadType specified.");
54+
}
55+
56+
if (!foundDefinition) {
57+
throw new ResponseStatusException(
58+
HttpStatus.BAD_REQUEST, "Specified payloadType is not a registered springwolf schema.");
59+
}
60+
}
61+
62+
private void publishMessage(String topic, MessageDto message, String schemaPayloadType) {
63+
try {
64+
Class<?> payloadClass = Class.forName(schemaPayloadType);
65+
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
66+
67+
log.debug("Publishing to sns topic {}: {}", topic, message);
68+
producer.send(topic, MessageBuilder.withPayload(payload).build());
69+
} catch (ClassNotFoundException | JsonProcessingException ex) {
70+
throw new ResponseStatusException(
71+
HttpStatus.BAD_REQUEST,
72+
MessageFormat.format(
73+
"Unable to create payload {0} from data: {1}", schemaPayloadType, message.getPayload()));
5874
}
5975
}
6076

springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/sns/SpringwolfSnsProducerConfiguration.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import io.awspring.cloud.sns.core.SnsTemplate;
66
import io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfSnsController;
7-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService;
87
import io.github.stavshamir.springwolf.producer.SpringwolfSnsProducer;
8+
import io.github.stavshamir.springwolf.schemas.SchemasService;
99
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1010
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1111
import org.springframework.context.annotation.Bean;
@@ -26,10 +26,8 @@ public class SpringwolfSnsProducerConfiguration {
2626
@Bean
2727
@ConditionalOnMissingBean
2828
public SpringwolfSnsController springwolfSnsController(
29-
AsyncApiDocketService asyncApiDocketService,
30-
SpringwolfSnsProducer springwolfSnsProducer,
31-
ObjectMapper objectMapper) {
32-
return new SpringwolfSnsController(asyncApiDocketService, springwolfSnsProducer, objectMapper);
29+
SchemasService schemasService, SpringwolfSnsProducer springwolfSnsProducer, ObjectMapper objectMapper) {
30+
return new SpringwolfSnsController(schemasService, springwolfSnsProducer, objectMapper);
3331
}
3432

3533
@Bean

springwolf-plugins/springwolf-sqs-plugin/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ dependencies {
3131
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
3232
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
3333

34+
implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}"
35+
3436
compileOnly "com.google.code.findbugs:jsr305:${jsr305Version}"
3537
permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}"
3638

0 commit comments

Comments
 (0)