diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/SearchType.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/SearchType.java index 5a6e815460ef..e96c24f29ff0 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/SearchType.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/SearchType.java @@ -25,6 +25,7 @@ import org.graylog.plugins.views.search.engine.BackendQuery; import org.graylog.plugins.views.search.rest.SearchTypeExecutionState; import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter; +import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType; import org.graylog.plugins.views.search.timeranges.DerivedTimeRange; import org.graylog2.contentpacks.ContentPackable; import org.graylog2.contentpacks.EntityDescriptorIds; @@ -140,7 +141,7 @@ interface Result { } @JsonAutoDetect - class Fallback implements SearchType { + class Fallback implements SearchEngineSearchType { @JsonProperty private String type; diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/elasticsearch/ElasticsearchQueryString.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/elasticsearch/ElasticsearchQueryString.java index a1bf4411abf1..c5afea8765e0 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/elasticsearch/ElasticsearchQueryString.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/elasticsearch/ElasticsearchQueryString.java @@ -23,13 +23,14 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.google.auto.value.AutoValue; import com.google.common.base.Strings; +import jakarta.validation.constraints.NotNull; +import org.graylog.plugins.views.search.SearchType; import org.graylog.plugins.views.search.engine.BackendQuery; +import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType; import javax.annotation.Nullable; - -import jakarta.validation.constraints.NotNull; - import java.util.Optional; +import java.util.Set; @AutoValue @JsonAutoDetect @@ -65,6 +66,11 @@ public static ElasticsearchQueryString create(final @JsonProperty("type") String @Override public abstract String queryString(); + @JsonIgnore + public Set> supportedSearchTypes() { + return Set.of(SearchEngineSearchType.class); + } + @JsonIgnore public boolean isEmpty() { String trimmed = queryString().trim(); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/BackendQuery.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/BackendQuery.java index f53d3d62967c..0c35d546ac53 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/BackendQuery.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/BackendQuery.java @@ -16,9 +16,13 @@ */ package org.graylog.plugins.views.search.engine; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.graylog.plugins.views.search.SearchType; import org.graylog.plugins.views.search.elasticsearch.ElasticsearchQueryString; +import java.util.Set; + @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = BackendQuery.TYPE_FIELD, @@ -30,4 +34,7 @@ public interface BackendQuery { String type(); String queryString(); + + @JsonIgnore + Set> supportedSearchTypes(); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/EngineBindings.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/EngineBindings.java index fbdc1b9e0848..602446e982af 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/EngineBindings.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/EngineBindings.java @@ -22,6 +22,7 @@ import org.graylog.plugins.views.search.engine.normalization.SearchNormalization; import org.graylog.plugins.views.search.engine.validation.DataWarehouseSearchValidator; import org.graylog.plugins.views.search.engine.validation.PluggableSearchValidation; +import org.graylog.plugins.views.search.engine.validation.SearchTypesMatchBackendQueryValidator; import org.graylog.plugins.views.search.engine.validation.SearchValidation; import org.graylog.plugins.views.search.engine.validation.TimeRangeValidator; @@ -35,6 +36,7 @@ protected void configure() { // Triggering set binder explicitly, so no injection errors are being caused if no implementation is bound. searchPostValidationNormalizerBinder(); registerSearchValidator(TimeRangeValidator.class); + registerSearchValidator(SearchTypesMatchBackendQueryValidator.class); //while DW searches are enterprise only, the validation should probably be present in gl-server, i.e. to discover invalid searches even if a client has lost the license registerSearchValidator(DataWarehouseSearchValidator.class); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/DataWarehouseSearchValidator.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/DataWarehouseSearchValidator.java index a28685512e8e..cbc9f72a0d8c 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/DataWarehouseSearchValidator.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/DataWarehouseSearchValidator.java @@ -17,7 +17,6 @@ package org.graylog.plugins.views.search.engine.validation; import com.google.common.collect.ImmutableSet; -import org.graylog.plugins.views.search.DataWarehouseSearchType; import org.graylog.plugins.views.search.Query; import org.graylog.plugins.views.search.Search; import org.graylog.plugins.views.search.SearchType; @@ -25,6 +24,7 @@ import org.graylog.plugins.views.search.errors.SearchError; import org.graylog.plugins.views.search.errors.SearchTypeError; import org.graylog.plugins.views.search.permissions.SearchUser; +import org.graylog.plugins.views.search.searchtypes.DataWarehouseSearchType; import java.util.Optional; import java.util.Set; @@ -58,14 +58,13 @@ public Set validate(final Query query, return Set.of(new QueryError(query, "Data Warehouse query can contain only one search type")); } final Optional first = searchTypes.stream().findFirst(); - if (!(first.get() instanceof DataWarehouseSearchType)) { - return Set.of(new SearchTypeError(query, first.get().id(), "Data Warehouse query can contain only data warehouse search types")); - } else { - final Set streams = first.get().streams(); - if (streams == null || streams.size() > 1) { - return Set.of(new SearchTypeError(query, first.get().id(), "Data Warehouse preview can be executed on only 1 stream, search type contained more")); - } + + final Set streams = first.get().streams(); + if (streams == null || streams.size() > 1) { + return Set.of(new SearchTypeError(query, first.get().id(), + "Data Warehouse preview can be executed on only 1 stream, search type contained more")); } + } return Set.of(); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidator.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidator.java new file mode 100644 index 000000000000..b41c7bc7e6f6 --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog.plugins.views.search.engine.validation; + +import org.graylog.plugins.views.search.Query; +import org.graylog.plugins.views.search.Search; +import org.graylog.plugins.views.search.errors.SearchError; +import org.graylog.plugins.views.search.errors.SearchTypeError; +import org.graylog.plugins.views.search.permissions.SearchUser; + +import java.util.Set; +import java.util.stream.Collectors; + +public class SearchTypesMatchBackendQueryValidator implements SearchValidator { + + public static final String INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG = "Invalid search type for given query type"; + + @Override + public Set validate(final Search search, + final SearchUser searchUser) { + return search.queries() + .stream() + .flatMap(query -> validate(query, searchUser).stream()) + .collect(Collectors.toSet()); + } + + @Override + public Set validate(final Query query, + final SearchUser searchUser) { + return query.searchTypes().stream() + .filter(searchType -> query.query() + .supportedSearchTypes() + .stream() + .anyMatch(supportedType -> !supportedType.isAssignableFrom(searchType.getClass()))) + .map(searchType -> new SearchTypeError(query, searchType.id(), INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG)) + .collect(Collectors.toSet()); + } +} diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/DataWarehouseSearchType.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/DataWarehouseSearchType.java similarity index 89% rename from graylog2-server/src/main/java/org/graylog/plugins/views/search/DataWarehouseSearchType.java rename to graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/DataWarehouseSearchType.java index ce1505360a43..ef6686c1710a 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/DataWarehouseSearchType.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/DataWarehouseSearchType.java @@ -14,7 +14,9 @@ * along with this program. If not, see * . */ -package org.graylog.plugins.views.search; +package org.graylog.plugins.views.search.searchtypes; + +import org.graylog.plugins.views.search.SearchType; /** * Marker interface for search types that are not search engine related, but Data Warehouse/Iceberg related diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/MessageList.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/MessageList.java index f816c9d02262..de28fb9615aa 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/MessageList.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/MessageList.java @@ -52,7 +52,7 @@ @AutoValue @JsonTypeName(MessageList.NAME) @JsonDeserialize(builder = MessageList.Builder.class) -public abstract class MessageList implements SearchType { +public abstract class MessageList implements SearchEngineSearchType { public static final String NAME = "messages"; @Override diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/SearchEngineSearchType.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/SearchEngineSearchType.java new file mode 100644 index 000000000000..5e0d6cf9e75d --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/SearchEngineSearchType.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog.plugins.views.search.searchtypes; + +import org.graylog.plugins.views.search.SearchType; + +/** + * Marker interface for search types that are search engine related. + */ +public interface SearchEngineSearchType extends SearchType { +} diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/events/EventList.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/events/EventList.java index c423edbe93bc..7d455232a71b 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/events/EventList.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/events/EventList.java @@ -29,6 +29,7 @@ import org.graylog.plugins.views.search.engine.BackendQuery; import org.graylog.plugins.views.search.rest.SearchTypeExecutionState; import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter; +import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType; import org.graylog.plugins.views.search.timeranges.DerivedTimeRange; import org.graylog2.contentpacks.EntityDescriptorIds; import org.graylog2.contentpacks.model.entities.EntityDescriptor; @@ -52,7 +53,7 @@ @AutoValue @JsonTypeName(EventList.NAME) @JsonDeserialize(builder = EventList.Builder.class) -public abstract class EventList implements SearchType, HasAttributeFilter { +public abstract class EventList implements SearchEngineSearchType, HasAttributeFilter { public static final int DEFAULT_PAGE_SIZE = 10; public static final String NAME = "events"; public static final Set KNOWN_ATTRIBUTES = Set.of("priority", "event_definition_id", "alert"); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/pivot/Pivot.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/pivot/Pivot.java index c83b1c6def23..50fd55299580 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/pivot/Pivot.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/searchtypes/pivot/Pivot.java @@ -28,6 +28,7 @@ import org.graylog.plugins.views.search.engine.BackendQuery; import org.graylog.plugins.views.search.rest.SearchTypeExecutionState; import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter; +import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType; import org.graylog.plugins.views.search.timeranges.DerivedTimeRange; import org.graylog2.contentpacks.EntityDescriptorIds; import org.graylog2.contentpacks.model.entities.EntityDescriptor; @@ -47,7 +48,7 @@ @AutoValue @JsonTypeName(Pivot.NAME) @JsonDeserialize(builder = Pivot.Builder.class) -public abstract class Pivot implements SearchType { +public abstract class Pivot implements SearchEngineSearchType { public static final String NAME = "pivot"; @Override diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidatorTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidatorTest.java new file mode 100644 index 000000000000..bf4ee92c5857 --- /dev/null +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/search/engine/validation/SearchTypesMatchBackendQueryValidatorTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog.plugins.views.search.engine.validation; + +import org.graylog.plugins.views.search.Query; +import org.graylog.plugins.views.search.SearchType; +import org.graylog.plugins.views.search.elasticsearch.ElasticsearchQueryString; +import org.graylog.plugins.views.search.errors.SearchError; +import org.graylog.plugins.views.search.permissions.SearchUser; +import org.graylog.plugins.views.search.searchtypes.MessageList; +import org.graylog.plugins.views.search.searchtypes.pivot.Pivot; +import org.graylog.plugins.views.search.searchtypes.pivot.buckets.Values; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.graylog.plugins.views.search.engine.validation.SearchTypesMatchBackendQueryValidator.INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class SearchTypesMatchBackendQueryValidatorTest { + + private SearchTypesMatchBackendQueryValidator toTest; + @Mock + private SearchUser searchUser; + + @BeforeEach + void setUp() { + toTest = new SearchTypesMatchBackendQueryValidator(); + } + + @Test + void testValidationIsOkOnProperSearchTypes() { + Query query = Query.builder() + .query(ElasticsearchQueryString.of("bayobongo")) + .searchTypes(Set.of( + MessageList.builder() + .id("messageListId") + .build(), + + Pivot.builder() + .rowGroups( + Values.builder() + .fields(List.of("id", "whatever")) + .limit(10) + .build() + ) + .series(List.of()) + .rollup(false) + .build() + )) + .build(); + + assertEquals(Set.of(), toTest.validate(query, searchUser)); + } + + @Test + void testValidationDiscoversWrongSearchTypes() { + final SearchType wrongSearchType = mock(SearchType.class); + doReturn("wrong!").when(wrongSearchType).id(); + Query query = Query.builder() + .id("query_id") + .query(ElasticsearchQueryString.of("bayobongo")) + .searchTypes(Set.of( + MessageList.builder() + .id("messageListId") + .build(), + + wrongSearchType + )) + .build(); + + final Set errors = toTest.validate(query, searchUser); + assertThat(errors) + .isNotNull() + .hasSize(1); + assertThat(errors.stream().findFirst().get().description()) + .isEqualTo(INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG); + + } +}