Skip to content

Commit

Permalink
Fix unescaped double quotes in nested event fields for TeamsNotificat…
Browse files Browse the repository at this point in the history
…ionV2 (#20389)

* Fix unescaped double quotes in nested event fields for TeamsNotificationV2

* Changelog
  • Loading branch information
kingzacko1 authored Sep 10, 2024
1 parent 5b33030 commit e8e8e46
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 22 deletions.
4 changes: 4 additions & 0 deletions changelog/unreleased/pr-20389.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type = "f"
message = "Fixed issue where invalid JSON characters being unescaped broke TeamsNotificationV2 notifications."

pulls = ["20389"]
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.floreysoft.jmte.Engine;
import com.google.common.annotations.VisibleForTesting;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.graylog.events.notifications.EventNotification;
import org.graylog.events.notifications.EventNotificationContext;
import org.graylog.events.notifications.EventNotificationException;
Expand All @@ -40,7 +41,6 @@
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -51,7 +51,7 @@ public class TeamsEventNotificationV2 implements EventNotification {

private static final Logger LOG = LoggerFactory.getLogger(TeamsEventNotificationV2.class);
private final EventNotificationService notificationCallbackService;
private final Engine templateEngine;
private final Engine jsonTemplateEngine;
private final NotificationService notificationService;
private final ObjectMapperProvider objectMapperProvider;
private final NodeId nodeId;
Expand All @@ -61,13 +61,13 @@ public class TeamsEventNotificationV2 implements EventNotification {
@Inject
public TeamsEventNotificationV2(EventNotificationService notificationCallbackService,
ObjectMapperProvider objectMapperProvider,
Engine templateEngine,
@Named("JsonSafe") Engine jsonTemplateEngine,
NotificationService notificationService,
NodeId nodeId, RequestClient requestClient,
HttpConfiguration httpConfiguration) {
this.notificationCallbackService = notificationCallbackService;
this.objectMapperProvider = requireNonNull(objectMapperProvider);
this.templateEngine = requireNonNull(templateEngine);
this.jsonTemplateEngine = requireNonNull(jsonTemplateEngine);
this.notificationService = requireNonNull(notificationService);
this.nodeId = requireNonNull(nodeId);
this.requestClient = requireNonNull(requestClient);
Expand Down Expand Up @@ -121,7 +121,7 @@ String generateBody(EventNotificationContext ctx, TeamsEventNotificationConfigV2
final List<MessageSummary> backlog = getMessageBacklog(ctx, config);
Map<String, Object> model = getCustomMessageModel(ctx, config.type(), backlog, config.timeZone());
try {
return templateEngine.transform(config.adaptiveCard(), model);
return jsonTemplateEngine.transform(config.adaptiveCard(), model);
} catch (Exception e) {
String error = "Invalid Custom Message template.";
LOG.error("{} [{}]", error, e.toString());
Expand All @@ -146,17 +146,8 @@ Map<String, Object> getCustomMessageModel(EventNotificationContext ctx, String t
final Map<String, Object> objectMap = objectMapperProvider.getForTimeZone(timeZone).convertValue(modelData, TypeReferences.MAP_STRING_OBJECT);
objectMap.put("type", type);
objectMap.put("http_external_uri", this.httpExternalUri);
final Map<String, Object> escapedModelMap = new HashMap<>();
objectMap.forEach((k, v) -> {
if (v instanceof String str) {
escapedModelMap.put(k, str.replace("\"", "\\\""));
} else {
escapedModelMap.put(k, v);
}
});
LOG.debug("Finalized model map: {}", escapedModelMap);

return escapedModelMap;

return objectMap;
}

public interface Factory extends EventNotification.Factory<TeamsEventNotificationV2> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.graylog.integrations.notifications.types.microsoftteams;

import com.floreysoft.jmte.Engine;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
Expand All @@ -30,6 +29,7 @@
import org.graylog.events.notifications.TemporaryEventNotificationException;
import org.graylog.events.processor.EventDefinitionDto;
import org.graylog.integrations.notifications.types.util.RequestClient;
import org.graylog2.bindings.providers.JsonSafeEngineProvider;
import org.graylog2.configuration.HttpConfiguration;
import org.graylog2.notifications.NotificationImpl;
import org.graylog2.notifications.NotificationService;
Expand Down Expand Up @@ -67,6 +67,106 @@ public class TeamsEventNotificationV2Test {

private final NodeId nodeId = new SimpleNodeId("12345");
private final MessageFactory messageFactory = new TestMessageFactory();
private final String defaultTemplate = """
{ "type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"version": "1.6",
"msTeams": { "width": "full" },
"body": [
{
"type": "TextBlock",
"size": "Large",
"weight": "Bolder",
"text": "${event_definition_title} triggered",
"style": "heading",
"fontType": "Default"
},
{
"type": "TextBlock",
"text": "${event_definition_description}",
"wrap": true
},
{
"type": "TextBlock",
"text": "Event Details",
"wrap": true
},
{
"type": "FactSet",
"facts": [
{
"title": "Type",
"value": "${event_definition_type}"
},
{
"title": "Timestamp",
"value": "${event.timestamp_processing}"
},
{
"title": "Message",
"value": "${event.message}"
},
{
"title": "Source",
"value": "${event.source}"
},
{
"title": "Key",
"value": "${event.key}"
},
{
"title": "Priority",
"value": "${event.priority}"
},
{
"title": "Alert",
"value": "${event.alert}"
},
{
"title": "Timerange Start",
"value": "${event.timerange_start}"
},
{
"title": "Timerange End",
"value": "${event.timerange_end}"
}
]
}${if event.fields},
{
"type": "TextBlock",
"text": "Event Fields",
"weight": "bolder",
"size": "medium"
},
{
"type": "FactSet",
"facts": [${foreach event.fields field}
{ "title": "${field.key}", "value": "${field.value}" }${if last_field}${else},${end}${end}
]
}${end}${if backlog},
{
"type": "TextBlock",
"text": "Backlog",
"weight": "bolder",
"size": "medium"
},
{
"type": "FactSet",
"facts": [${foreach backlog message}
{ "title": "Message", "value": "${message.message}" }${if last_message}${else},${end}${end}
]
}${end}
],
"$schema": "[http://adaptivecards.io/schemas/adaptive-card.json](https://link.edgepilot.com/s/8e5962e4/2Jj9cedkLka5KIsBRuMOIg?u=http://adaptivecards.io/schemas/adaptive-card.json)",
"rtl": false
}
}
]
}""";

@Mock
NotificationService mockNotificationService;
Expand All @@ -89,23 +189,30 @@ public void setUp() {

teamsEventNotification = new TeamsEventNotificationV2(notificationCallbackService,
new ObjectMapperProvider(),
Engine.createEngine(),
new JsonSafeEngineProvider().get(),
mockNotificationService,
nodeId,
mockrequestClient,
new HttpConfiguration());
}

@Test
public void testEscapedQuotes() {
public void testEscapedQuotes() throws PermanentEventNotificationException {
if (eventNotificationContext.eventDefinition().isPresent()) {
EventDefinitionDto definition = eventNotificationContext.eventDefinition().get();
definition = definition.toBuilder().description("A Description with \"Double Quotes\"").build();
eventNotificationContext = eventNotificationContext.toBuilder().eventDefinition(definition).build();
}
List<MessageSummary> messageSummaries = generateMessageSummaries(50);
Map<String, Object> customMessageModel = teamsEventNotification.getCustomMessageModel(eventNotificationContext, notificationConfig.type(), messageSummaries, DateTimeZone.UTC);
assertThat(customMessageModel.get("event_definition_description")).isEqualTo("A Description with \\\"Double Quotes\\\"");
when(notificationCallbackService.getBacklogForEvent(any())).thenReturn(generateMessageSummariesWithDoubleQuotes(5));
TeamsEventNotificationConfigV2 config = TeamsEventNotificationConfigV2.builder()
.adaptiveCard(defaultTemplate)
.backlogSize(5)
.timeZone(DateTimeZone.UTC)
.webhookUrl("http://localhost:12345")
.build();
String body = teamsEventNotification.generateBody(eventNotificationContext, config);
assertThat(body).contains("A Description with \\\"Double Quotes\\\"");
assertThat(body).contains("Test message1 with \\\"Double Quotes\\\"");
}

@Test
Expand Down Expand Up @@ -246,6 +353,15 @@ private ImmutableList<MessageSummary> generateMessageSummaries(int size) {
return ImmutableList.copyOf(messageSummaries);
}

private ImmutableList<MessageSummary> generateMessageSummariesWithDoubleQuotes(int size) {
List<MessageSummary> messageSummaries = new ArrayList<>();
for (int i = 0; i < size; i++) {
MessageSummary summary = new MessageSummary("graylog_" + i, messageFactory.createMessage("Test message" + i + " with \"Double Quotes\"", "source" + i, new DateTime(2020, 9, 6, 17, 0, DateTimeZone.UTC)));
messageSummaries.add(summary);
}
return ImmutableList.copyOf(messageSummaries);
}

private TeamsEventNotificationConfigV2 buildInvalidTemplate() {
TeamsEventNotificationConfigV2.Builder builder = TeamsEventNotificationConfigV2.builder();
builder.adaptiveCard("{${if backlog}\"invalid_json\": true }");
Expand Down

0 comments on commit e8e8e46

Please sign in to comment.