blob: 859c21a49345772c7bf04d39142c2d2baf610e3f [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.metron.rest.controller;
import com.google.common.collect.ImmutableMap;
import org.adrianwalker.multilinestring.Multiline;
import org.apache.metron.indexing.dao.InMemoryDao;
import org.apache.metron.indexing.dao.SearchIntegrationTest;
import org.apache.metron.indexing.dao.search.FieldType;
import org.apache.metron.rest.service.AlertsUIService;
import org.apache.metron.rest.service.SensorIndexingConfigService;
import org.json.simple.parser.ParseException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.HashMap;
import java.util.Map;
import static org.apache.metron.integration.utils.TestUtils.assertEventually;
import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(TEST_PROFILE)
public class SearchControllerIntegrationTest extends DaoControllerTest {
/**
* {
* "indices": [],
* "query": "*",
* "from": 0,
* "size": 10,
* "sort": [
* {
* "field": "timestamp",
* "sortOrder": "desc"
* }
* ],
* "facetFields": []
* }
*/
@Multiline
public static String defaultQuery;
/**
* {
* "facetFields": ["ip_src_port"]
* }
*/
@Multiline
public static String alertProfile;
@Autowired
private SensorIndexingConfigService sensorIndexingConfigService;
@Autowired
private AlertsUIService alertsUIService;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
private String searchUrl = "/api/v1/search";
private String user = "user";
private String password = "password";
@BeforeEach
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
ImmutableMap<String, String> testData = ImmutableMap.of(
"bro_index_2017.01.01.01", SearchIntegrationTest.broData,
"snort_index_2017.01.01.01", SearchIntegrationTest.snortData
);
loadTestData(testData);
loadColumnTypes();
loadFacetCounts();
}
@AfterEach
public void cleanup() throws Exception {
InMemoryDao.clear();
}
@Test
public void testSecurity() throws Exception {
this.mockMvc.perform(post(searchUrl + "/search").with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.allQuery))
.andExpect(status().isUnauthorized());
}
@Test
public void testSearchWithDefaults() throws Exception {
sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{
put("index", "bro");
}});
assertEventually(() -> this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(defaultQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(5))
.andExpect(jsonPath("$.results[0].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[0].source.timestamp").value(5))
.andExpect(jsonPath("$.results[1].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[1].source.timestamp").value(4))
.andExpect(jsonPath("$.results[2].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[2].source.timestamp").value(3))
.andExpect(jsonPath("$.results[3].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[3].source.timestamp").value(2))
.andExpect(jsonPath("$.results[4].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[4].source.timestamp").value(1))
.andExpect(jsonPath("$.facetCounts.*", hasSize(2)))
.andExpect(jsonPath("$.facetCounts.source:type.*", hasSize(1)))
.andExpect(jsonPath("$.facetCounts.source:type['bro']").value(5))
.andExpect(jsonPath("$.facetCounts.ip_src_addr.*", hasSize(2)))
.andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.1']").value(3))
.andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.2']").value(1))
);
sensorIndexingConfigService.delete("bro");
}
@Test
public void testSearchWithAlertProfileFacetFields() throws Exception {
assertEventually(() -> this.mockMvc.perform(
post("/api/v1/alerts/ui/settings").with(httpBasic(user, password)).with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(alertProfile))
.andExpect(status().isOk())
);
assertEventually(() -> this.mockMvc.perform(
post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(defaultQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.facetCounts.*", hasSize(1)))
.andExpect(jsonPath("$.facetCounts.ip_src_port.*", hasSize(2)))
.andExpect(jsonPath("$.facetCounts.ip_src_port['8010']").value(1))
.andExpect(jsonPath("$.facetCounts.ip_src_port['8009']").value(2))
);
alertsUIService.deleteAlertsUIUserSettings(user);
}
@Test
public void testColumnMetadataUsingDefaultIndices() throws Exception {
// Setup the default indices of bro and snort
sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{
put("index", "bro");
}});
sensorIndexingConfigService.save("snort", new HashMap<String, Object>() {{
put("index", "snort");
}});
// Pass in an empty list to trigger using default indices
assertEventually(() -> this.mockMvc.perform(post(searchUrl + "/column/metadata").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content("[]"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(5)))
.andExpect(jsonPath("$.common_string_field").value("text"))
.andExpect(jsonPath("$.common_integer_field").value("integer"))
.andExpect(jsonPath("$.bro_field").value("boolean"))
.andExpect(jsonPath("$.snort_field").value("double"))
.andExpect(jsonPath("$.duplicate_field").value("other"))
);
sensorIndexingConfigService.delete("bro");
sensorIndexingConfigService.delete("snort");
}
@Test
public void test() throws Exception {
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.allQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(10))
.andExpect(jsonPath("$.results[0].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[0].source.timestamp").value(10))
.andExpect(jsonPath("$.results[1].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[1].source.timestamp").value(9))
.andExpect(jsonPath("$.results[2].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[2].source.timestamp").value(8))
.andExpect(jsonPath("$.results[3].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[3].source.timestamp").value(7))
.andExpect(jsonPath("$.results[4].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[4].source.timestamp").value(6))
.andExpect(jsonPath("$.results[5].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[5].source.timestamp").value(5))
.andExpect(jsonPath("$.results[6].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[6].source.timestamp").value(4))
.andExpect(jsonPath("$.results[7].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[7].source.timestamp").value(3))
.andExpect(jsonPath("$.results[8].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[8].source.timestamp").value(2))
.andExpect(jsonPath("$.results[9].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[9].source.timestamp").value(1));
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.filterQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(3))
.andExpect(jsonPath("$.results[0].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[0].source.timestamp").value(9))
.andExpect(jsonPath("$.results[1].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[1].source.timestamp").value(7))
.andExpect(jsonPath("$.results[2].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[2].source.timestamp").value(1));
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.sortQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(10))
.andExpect(jsonPath("$.results[0].source.ip_src_port").value(8001))
.andExpect(jsonPath("$.results[1].source.ip_src_port").value(8002))
.andExpect(jsonPath("$.results[2].source.ip_src_port").value(8003))
.andExpect(jsonPath("$.results[3].source.ip_src_port").value(8004))
.andExpect(jsonPath("$.results[4].source.ip_src_port").value(8005))
.andExpect(jsonPath("$.results[5].source.ip_src_port").value(8006))
.andExpect(jsonPath("$.results[6].source.ip_src_port").value(8007))
.andExpect(jsonPath("$.results[7].source.ip_src_port").value(8008))
.andExpect(jsonPath("$.results[8].source.ip_src_port").value(8009))
.andExpect(jsonPath("$.results[9].source.ip_src_port").value(8010));
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.paginationQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(10))
.andExpect(jsonPath("$.results[0].source.source:type").value("snort"))
.andExpect(jsonPath("$.results[0].source.timestamp").value(6))
.andExpect(jsonPath("$.results[1].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[1].source.timestamp").value(5))
.andExpect(jsonPath("$.results[2].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[2].source.timestamp").value(4));
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.indexQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.total").value(5))
.andExpect(jsonPath("$.results[0].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[0].source.timestamp").value(5))
.andExpect(jsonPath("$.results[1].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[1].source.timestamp").value(4))
.andExpect(jsonPath("$.results[2].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[2].source.timestamp").value(3))
.andExpect(jsonPath("$.results[3].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[3].source.timestamp").value(2))
.andExpect(jsonPath("$.results[4].source.source:type").value("bro"))
.andExpect(jsonPath("$.results[4].source.timestamp").value(1));
this.mockMvc.perform(post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.exceededMaxResultsQuery))
.andExpect(status().isInternalServerError())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.responseCode").value(500))
.andExpect(jsonPath("$.message").value("Search result size must be less than 100"));
this.mockMvc.perform(post(searchUrl + "/group").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(SearchIntegrationTest.groupByQuery))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(2)))
.andExpect(jsonPath("$.groupedBy").value("is_alert"))
.andExpect(jsonPath("$.groupResults.*", hasSize(1)))
.andExpect(jsonPath("$.groupResults[0].*", hasSize(5)))
.andExpect(jsonPath("$.groupResults[0].key").value("is_alert_value"))
.andExpect(jsonPath("$.groupResults[0].total").value(10))
.andExpect(jsonPath("$.groupResults[0].groupedBy").value("latitude"))
.andExpect(jsonPath("$.groupResults[0].groupResults.*", hasSize(1)))
.andExpect(jsonPath("$.groupResults[0].groupResults[0].*", hasSize(3)))
.andExpect(jsonPath("$.groupResults[0].groupResults[0].key").value("latitude_value"))
.andExpect(jsonPath("$.groupResults[0].groupResults[0].total").value(10))
.andExpect(jsonPath("$.groupResults[0].groupResults[0].score").value(50));
this.mockMvc.perform(post(searchUrl + "/column/metadata").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content("[\"bro\",\"snort\"]"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(5)))
.andExpect(jsonPath("$.common_string_field").value("text"))
.andExpect(jsonPath("$.common_integer_field").value("integer"))
.andExpect(jsonPath("$.bro_field").value("boolean"))
.andExpect(jsonPath("$.snort_field").value("double"))
.andExpect(jsonPath("$.duplicate_field").value("other"));
this.mockMvc.perform(post(searchUrl + "/column/metadata").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content("[\"bro\"]"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(4)))
.andExpect(jsonPath("$.common_string_field").value("text"))
.andExpect(jsonPath("$.common_integer_field").value("integer"))
.andExpect(jsonPath("$.bro_field").value("boolean"))
.andExpect(jsonPath("$.duplicate_field").value("date"));
this.mockMvc.perform(post(searchUrl + "/column/metadata").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content("[\"snort\"]"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(4)))
.andExpect(jsonPath("$.common_string_field").value("text"))
.andExpect(jsonPath("$.common_integer_field").value("integer"))
.andExpect(jsonPath("$.snort_field").value("double"))
.andExpect(jsonPath("$.duplicate_field").value("long"));
this.mockMvc.perform(post(searchUrl + "/column/metadata").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content("[\"someindex\"]"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(0)));
}
private void loadColumnTypes() throws ParseException {
Map<String, Map<String, FieldType>> columnTypes = new HashMap<>();
Map<String, FieldType> broTypes = new HashMap<>();
broTypes.put("common_string_field", FieldType.TEXT);
broTypes.put("common_integer_field", FieldType.INTEGER);
broTypes.put("bro_field", FieldType.BOOLEAN);
broTypes.put("duplicate_field", FieldType.DATE);
Map<String, FieldType> snortTypes = new HashMap<>();
snortTypes.put("common_string_field", FieldType.TEXT);
snortTypes.put("common_integer_field", FieldType.INTEGER);
snortTypes.put("snort_field", FieldType.DOUBLE);
snortTypes.put("duplicate_field", FieldType.LONG);
columnTypes.put("bro", broTypes);
columnTypes.put("snort", snortTypes);
InMemoryDao.setColumnMetadata(columnTypes);
}
private void loadFacetCounts() {
Map<String, Map<String, Long>> facetCounts = new HashMap<>();
Map<String, Long> ipSrcAddrCounts = new HashMap<>();
ipSrcAddrCounts.put("192.168.1.1", 3L);
ipSrcAddrCounts.put("192.168.1.2", 1L);
Map<String, Long> ipSrcPortCounts = new HashMap<>();
ipSrcPortCounts.put("8010", 1L);
ipSrcPortCounts.put("8009", 2L);
Map<String, Long> sourceTypeCounts = new HashMap<>();
sourceTypeCounts.put("bro", 5L);
facetCounts.put("ip_src_addr", ipSrcAddrCounts);
facetCounts.put("ip_src_port", ipSrcPortCounts);
facetCounts.put("source:type", sourceTypeCounts);
InMemoryDao.setFacetCounts(facetCounts);
}
}