blob: 28a4098731dca151ea424bb392dc41ab12a12169 [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 org.adrianwalker.multilinestring.Multiline;
import org.apache.metron.integration.ComponentRunner;
import org.apache.metron.integration.UnableToStartException;
import org.apache.metron.integration.components.KafkaComponent;
import org.apache.metron.rest.service.AlertsUIService;
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 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.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(TEST_PROFILE)
public class AlertsUIControllerIntegrationTest {
/**
* [
* {
* "is_alert": true,
* "field": "value1"
* },
* {
* "is_alert": true,
* "field": "value2"
* }
* ]
*/
@Multiline
public static String alerts;
/**
* {
* "tableColumns": ["user1_field"],
* "savedSearches": [
* {
* "name": "user1 search 1",
* "searchRequest": {
* "from": 0,
* "indices": ["bro"],
* "query": "*",
* "size": 5
* }
* },
* {
* "name": "user1 search 2",
* "searchRequest": {
* "from": 10,
* "indices": ["snort"],
* "query": "*",
* "size": 10
* }
* }
* ]
* }
*/
@Multiline
public static String user1AlertUserSettingsJson;
/**
* {
* "tableColumns": ["user2_field"],
* "savedSearches": [
* {
* "name": "user2 search 1",
* "searchRequest": {
* "from": 0,
* "indices": ["bro", "snort"],
* "query": "ip_src_addr:192.168.1.1",
* "size": 100
* }
* }
* ]
* }
*/
@Multiline
public static String user2AlertUserSettingsJson;
// A bug in Spring and/or Kafka forced us to move into a component that is spun up and down per test-case
// Given the large spinup time of components, please avoid this pattern until we upgrade Spring.
// See: https://issues.apache.org/jira/browse/METRON-1009
@Autowired
private KafkaComponent kafkaWithZKComponent;
private ComponentRunner runner;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Autowired
private AlertsUIService alertsUIService;
private String alertUrl = "/api/v1/alerts/ui";
private String user1 = "user1";
private String user2 = "user2";
private String admin = "admin";
private String password = "password";
@BeforeEach
public void setup() throws Exception {
for (String user : alertsUIService.findAllAlertsUIUserSettings().keySet()) {
alertsUIService.deleteAlertsUIUserSettings(user);
}
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
}
@Test
public void testSecurity() throws Exception {
this.mockMvc.perform(post(alertUrl + "/escalate").with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts))
.andExpect(status().isUnauthorized());
this.mockMvc.perform(get(alertUrl + "/settings"))
.andExpect(status().isUnauthorized());
this.mockMvc.perform(get(alertUrl + "/settings/all"))
.andExpect(status().isUnauthorized());
this.mockMvc.perform(post(alertUrl + "/settings").with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(user1AlertUserSettingsJson))
.andExpect(status().isUnauthorized());
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(user1, password)).with(csrf()))
.andExpect(status().isForbidden());
this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(user1, password)).with(csrf()))
.andExpect(status().isForbidden());
}
@Test
public void escalateShouldEscalateAlerts() throws Exception {
startKafka();
this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user1, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts))
.andExpect(status().isOk());
stopKafka();
}
@Test
public void testAlertProfiles() throws Exception {
emptyProfileShouldReturnNotFound();
alertsProfilesShouldBeCreatedOrUpdated();
alertsProfilesShouldBeProperlyDeleted();
}
/** Ensures a 404 is returned when an alerts profile cannot be found. In the case of an admin getting
* all profiles, an empty list should be returned. This tests depends on the alertsProfileRepository
* being empty.
*
* @throws Exception
*/
private void emptyProfileShouldReturnNotFound() throws Exception {
// user1 should get a 404 because an alerts profile has not been created
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
.andExpect(status().isNotFound());
// user2 should get a 404 because an alerts profile has not been created
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
.andExpect(status().isNotFound());
// getting all alerts profiles should return an empty list
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(0)));
}
/** Ensures users can update their profiles independently of other users. When user1 updates an
* alerts profile, alerts profile for user2 should not be affected. Tests that an initial update
* returns a 201 status and subsequent updates return 200 statuses. A call to get all alerts profiles
* by an admin user should also work properly. This tests depends on the alertsProfileRepository
* being empty initially.
*
* @throws Exception
*/
private void alertsProfilesShouldBeCreatedOrUpdated() throws Exception {
// user1 creates their alerts profile
this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user1, password)).with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(user1AlertUserSettingsJson))
.andExpect(status().isCreated());
// user1 updates their alerts profile
this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user1, password)).with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(user1AlertUserSettingsJson))
.andExpect(status().isOk());
// user1 gets their alerts profile
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json(user1AlertUserSettingsJson));
// user2 alerts profile should still be empty
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
.andExpect(status().isNotFound());
// getting all alerts profiles should only return user1's
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + "}"));
// user2 creates their alerts profile
this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user2, password)).with(csrf())
.contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
.content(user2AlertUserSettingsJson))
.andExpect(status().isCreated());
// user2 updates their alerts profile
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json(user1AlertUserSettingsJson));
// user2 gets their alerts profile
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json(user2AlertUserSettingsJson));
// getting all alerts profiles should return both
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + ",\"" + user2 + "\": " + user2AlertUserSettingsJson + "}"));
}
/** Ensures users can delete their profiles independently of other users. When user1 deletes an
* alerts profile, alerts profile for user2 should not be deleted. This tests depends on alerts
* profiles existing for user1 and user2.
*
* @throws Exception
*/
private void alertsProfilesShouldBeProperlyDeleted() throws Exception {
// user1 deletes their profile
this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(admin, password)))
.andExpect(status().isOk());
// user1 should get a 404 when trying to delete an alerts profile that doesn't exist
this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(admin, password)))
.andExpect(status().isNotFound());
// user1 should get a 404 when trying to retrieve their alerts profile
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
.andExpect(status().isNotFound());
// user2's alerts profile should still exist
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json(user2AlertUserSettingsJson));
// getting all alerts profiles should only return user2's
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(content().json("{\"" + user2 + "\": " + user2AlertUserSettingsJson + "}"));
// user2 deletes their profile
this.mockMvc.perform(delete(alertUrl + "/settings/user2").with(httpBasic(admin, password)))
.andExpect(status().isOk());
// user2 should get a 404 when trying to delete an alerts profile that doesn't exist
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
.andExpect(status().isNotFound());
// user2 should get a 404 when trying to retrieve their alerts profile
this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
.andExpect(status().isNotFound());
// getting all alerts profiles should return an empty list
this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(jsonPath("$.*", hasSize(0)));
}
private void startKafka() {
runner = new ComponentRunner.Builder()
.withComponent("kafka", kafkaWithZKComponent)
.withCustomShutdownOrder(new String[]{"kafka"})
.build();
try {
runner.start();
} catch (UnableToStartException e) {
e.printStackTrace();
}
}
private void stopKafka() {
runner.stop();
}
}