Skip to content

Commit

Permalink
Merge pull request #2102 from beyonnex-io/bugfix/wot-action-validation
Browse files Browse the repository at this point in the history
fix WoT action validation was only done for application/json content-type
  • Loading branch information
thjaeckle authored Jan 23, 2025
2 parents fbe5686 + 69c7f34 commit 8380910
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -93,6 +94,7 @@
import org.eclipse.ditto.things.model.signals.commands.modify.ThingModifyCommand;
import org.eclipse.ditto.wot.api.validator.WotThingModelValidator;
import org.eclipse.ditto.wot.integration.DittoWotIntegration;
import org.eclipse.ditto.wot.validation.WotThingModelPayloadValidationException;

/**
* Enforcer responsible for enforcing {@link ThingCommand}s and filtering {@link ThingCommandResponse}s utilizing the
Expand Down Expand Up @@ -572,29 +574,36 @@ private CompletionStage<Boolean> doesThingExist() {
private CompletionStage<MessageCommand<?, ?>> performWotBasedMessageCommandValidation(
final MessageCommand<?, ?> messageCommand
) {
if (isJsonMessageContent(messageCommand.getMessage())) {
@SuppressWarnings("unchecked") final Message<JsonValue> message =
((MessageCommand<JsonValue, ?>) messageCommand)
.getMessage();

final MessageDirection messageDirection = message.getDirection();
final JsonValue messageCommandPayload = message
.getPayload()
.orElse(null);

if (messageCommand instanceof SendThingMessage<?> sendThingMessage) {
return performWotBasedThingMessageValidation(messageCommand, sendThingMessage, messageDirection,
messageCommandPayload
).thenApply(aVoid -> messageCommand);
} else if (messageCommand instanceof SendFeatureMessage<?> sendFeatureMessage) {
final String featureId = sendFeatureMessage.getFeatureId();
return performWotBasedFeatureMessageValidation(messageCommand, sendFeatureMessage, featureId,
messageDirection, messageCommandPayload
).thenApply(aVoid -> messageCommand);

} else {
return CompletableFuture.completedFuture(messageCommand);
@SuppressWarnings("unchecked") final Message<JsonValue> message =
((MessageCommand<JsonValue, ?>) messageCommand)
.getMessage();

// lazily only supply JsonValue if validation is enabled for the message:
final Supplier<JsonValue> messageCommandPayloadSupplier = () -> {
if (message.getPayload().isPresent() && !isJsonMessageContent(message)) {
throw WotThingModelPayloadValidationException
.newBuilder("Could not validate non-JSON message content type <" +
message.getContentType().orElse("?") + "> for message subject " +
"<" + message.getSubject() + ">"
)
.dittoHeaders(messageCommand.getDittoHeaders())
.build();
}

return message.getPayload().orElse(null);
};

final MessageDirection messageDirection = message.getDirection();
if (messageCommand instanceof SendThingMessage<?> sendThingMessage) {
return performWotBasedThingMessageValidation(messageCommand, sendThingMessage, messageDirection,
messageCommandPayloadSupplier
).thenApply(aVoid -> messageCommand);
} else if (messageCommand instanceof SendFeatureMessage<?> sendFeatureMessage) {
final String featureId = sendFeatureMessage.getFeatureId();
return performWotBasedFeatureMessageValidation(messageCommand, sendFeatureMessage, featureId,
messageDirection, messageCommandPayloadSupplier
).thenApply(aVoid -> messageCommand);

} else {
return CompletableFuture.completedFuture(messageCommand);
}
Expand All @@ -603,23 +612,23 @@ private CompletionStage<Boolean> doesThingExist() {
private CompletionStage<Void> performWotBasedThingMessageValidation(final MessageCommand<?, ?> messageCommand,
final SendThingMessage<?> sendThingMessage,
final MessageDirection messageDirection,
@Nullable final JsonValue messageCommandPayload
final Supplier<JsonValue> messageCommandPayloadSupplier
) {
return resolveThingDefinition()
.thenCompose(optThingDefinition -> {
if (messageDirection == MessageDirection.TO) {
return thingModelValidator.validateThingActionInput(
optThingDefinition.orElse(null),
sendThingMessage.getMessage().getSubject(),
messageCommandPayload,
messageCommandPayloadSupplier,
sendThingMessage.getResourcePath(),
sendThingMessage.getDittoHeaders()
);
} else if (messageDirection == MessageDirection.FROM) {
return thingModelValidator.validateThingEventData(
optThingDefinition.orElse(null),
sendThingMessage.getMessage().getSubject(),
messageCommandPayload,
messageCommandPayloadSupplier,
sendThingMessage.getResourcePath(),
sendThingMessage.getDittoHeaders()
);
Expand All @@ -637,7 +646,7 @@ private CompletionStage<Void> performWotBasedFeatureMessageValidation(final Mess
final SendFeatureMessage<?> sendFeatureMessage,
final String featureId,
final MessageDirection messageDirection,
@Nullable final JsonValue messageCommandPayload
final Supplier<JsonValue> messageCommandPayloadSupplier
) {
return resolveThingAndFeatureDefinition(featureId)
.thenCompose(optDefinitionPair -> {
Expand All @@ -647,7 +656,7 @@ private CompletionStage<Void> performWotBasedFeatureMessageValidation(final Mess
optDefinitionPair.second().orElse(null),
featureId,
sendFeatureMessage.getMessage().getSubject(),
messageCommandPayload,
messageCommandPayloadSupplier,
sendFeatureMessage.getResourcePath(),
sendFeatureMessage.getDittoHeaders()
);
Expand All @@ -657,7 +666,7 @@ private CompletionStage<Void> performWotBasedFeatureMessageValidation(final Mess
optDefinitionPair.second().orElse(null),
featureId,
sendFeatureMessage.getMessage().getSubject(),
messageCommandPayload,
messageCommandPayloadSupplier,
sendFeatureMessage.getResourcePath(),
sendFeatureMessage.getDittoHeaders()
);
Expand All @@ -674,44 +683,63 @@ private CompletionStage<Void> performWotBasedFeatureMessageValidation(final Mess
private CompletionStage<MessageCommandResponse<?, ?>> performWotBasedMessageCommandResponseValidation(
final MessageCommandResponse<?, ?> messageCommandResponse
) {
if (isJsonMessageContent(messageCommandResponse.getMessage())) {
@SuppressWarnings("unchecked") final Message<JsonValue> message =
((MessageCommandResponse<JsonValue, ?>) messageCommandResponse)
.getMessage();

final MessageDirection messageDirection = message.getDirection();
final JsonValue messageCommandPayload = message
.getPayload()
.orElse(null);

if (messageDirection == MessageDirection.TO &&
messageCommandResponse instanceof SendThingMessageResponse<?> sendThingMessageResponse) {
return resolveThingDefinition()
.thenCompose(optThingDefinition -> thingModelValidator.validateThingActionOutput(
optThingDefinition.orElse(null),
sendThingMessageResponse.getMessage().getSubject(),
messageCommandPayload,
sendThingMessageResponse.getResourcePath(),
sendThingMessageResponse.getDittoHeaders()
))
.thenApply(aVoid -> messageCommandResponse);
} else if (messageDirection == MessageDirection.TO &&
messageCommandResponse instanceof SendFeatureMessageResponse<?> sendFeatureMessageResponse) {
final String featureId = sendFeatureMessageResponse.getFeatureId();
return resolveThingAndFeatureDefinition(featureId)
.thenCompose(optDefinitionPair -> thingModelValidator.validateFeatureActionOutput(
optDefinitionPair.first().orElse(null),
optDefinitionPair.second().orElse(null),
featureId,
sendFeatureMessageResponse.getMessage().getSubject(),
messageCommandPayload,
sendFeatureMessageResponse.getResourcePath(),
sendFeatureMessageResponse.getDittoHeaders()
))
.thenApply(aVoid -> messageCommandResponse);
} else {
return CompletableFuture.completedFuture(messageCommandResponse);
@SuppressWarnings("unchecked") final Message<JsonValue> message =
((MessageCommandResponse<JsonValue, ?>) messageCommandResponse)
.getMessage();

if (message.getPayload().isPresent() && !isJsonMessageContent(message)) {
return CompletableFuture.failedFuture(
WotThingModelPayloadValidationException
.newBuilder("Could not validate non-JSON message content type <" +
message.getContentType().orElse("?") + "> for message response subject " +
"<" + message.getSubject() + ">"
)
.dittoHeaders(messageCommandResponse.getDittoHeaders())
.build()
);
}

// lazily only supply JsonValue if validation is enabled for the message:
final Supplier<JsonValue> messageCommandPayloadSupplier = () -> {
if (message.getPayload().isPresent() && !isJsonMessageContent(message)) {
throw WotThingModelPayloadValidationException
.newBuilder("Could not validate non-JSON message content type <" +
message.getContentType().orElse("?") + "> for message response subject " +
"<" + message.getSubject() + ">"
)
.dittoHeaders(messageCommandResponse.getDittoHeaders())
.build();
}

return message.getPayload().orElse(null);
};

final MessageDirection messageDirection = message.getDirection();
if (messageDirection == MessageDirection.TO &&
messageCommandResponse instanceof SendThingMessageResponse<?> sendThingMessageResponse) {
return resolveThingDefinition()
.thenCompose(optThingDefinition -> thingModelValidator.validateThingActionOutput(
optThingDefinition.orElse(null),
sendThingMessageResponse.getMessage().getSubject(),
messageCommandPayloadSupplier,
sendThingMessageResponse.getResourcePath(),
sendThingMessageResponse.getDittoHeaders()
))
.thenApply(aVoid -> messageCommandResponse);
} else if (messageDirection == MessageDirection.TO &&
messageCommandResponse instanceof SendFeatureMessageResponse<?> sendFeatureMessageResponse) {
final String featureId = sendFeatureMessageResponse.getFeatureId();
return resolveThingAndFeatureDefinition(featureId)
.thenCompose(optDefinitionPair -> thingModelValidator.validateFeatureActionOutput(
optDefinitionPair.first().orElse(null),
optDefinitionPair.second().orElse(null),
featureId,
sendFeatureMessageResponse.getMessage().getSubject(),
messageCommandPayloadSupplier,
sendFeatureMessageResponse.getResourcePath(),
sendFeatureMessageResponse.getDittoHeaders()
))
.thenApply(aVoid -> messageCommandResponse);
} else {
return CompletableFuture.completedFuture(messageCommandResponse);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ public void correlationIdDifferentInCaseOfConflict() {

supervisor.tell(message, getRef());

expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);
expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);

final var firstPublishRead = expectPubsubMessagePublish(message.getEntityId());
Expand All @@ -315,6 +316,7 @@ public void correlationIdDifferentInCaseOfConflict() {

supervisor.tell(message, getRef());

expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);
expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);

final var secondPublishRead = expectPubsubMessagePublish(message.getEntityId());
Expand Down Expand Up @@ -351,6 +353,7 @@ public void acceptMessageCommandByPolicy() {
final MessageCommand<?, ?> msgCommand = thingMessageCommand("abc");
supervisor.tell(msgCommand, getRef());

expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);
expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);

expectPubsubMessagePublish(msgCommand.getEntityId());
Expand Down Expand Up @@ -378,6 +381,7 @@ public void acceptFeatureMessageCommandByPolicy() {
final MessageCommand<?, ?> msgCommand = featureMessageCommand();
supervisor.tell(msgCommand, getRef());

expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);
expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse);

expectPubsubMessagePublish(msgCommand.getEntityId());
Expand Down
Loading

0 comments on commit 8380910

Please sign in to comment.