From 010b5c23a5d1ae704454c36ba30bc2364b8eff5b Mon Sep 17 00:00:00 2001 From: merrimanr Date: Fri, 4 Aug 2017 15:24:11 -0500 Subject: [PATCH 01/11] initial commit --- metron-interface/metron-rest-client/pom.xml | 11 + .../metron/rest/converter/JsonConverter.java | 49 ++++ .../metron/rest/model/AlertsProfile.java | 64 +++++ .../apache/metron/rest/model/SavedSearch.java | 45 ++++ metron-interface/metron-rest/pom.xml | 76 ++++++ .../metron/rest/MetronRestConstants.java | 3 + .../metron/rest/config/JpaConfiguration.java | 51 ++++ .../metron/rest/config/WebSecurityConfig.java | 13 +- .../controller/AlertsProfileController.java | 75 ++++++ .../repository/AlertsProfileRepository.java | 25 ++ .../metron/rest/security/SecurityUtils.java | 40 ++++ .../rest/service/AlertsProfileService.java | 32 +++ .../impl/AlertsProfileServiceImpl.java | 62 +++++ .../src/main/resources/application.yml | 4 +- .../src/main/resources/log4j.properties | 2 +- ...lertsProfileControllerIntegrationTest.java | 219 ++++++++++++++++++ 16 files changed, 765 insertions(+), 6 deletions(-) create mode 100644 metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java create mode 100644 metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java create mode 100644 metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java create mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java create mode 100644 metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java diff --git a/metron-interface/metron-rest-client/pom.xml b/metron-interface/metron-rest-client/pom.xml index 21e4ffc591..95f1bc2ada 100644 --- a/metron-interface/metron-rest-client/pom.xml +++ b/metron-interface/metron-rest-client/pom.xml @@ -23,6 +23,7 @@ metron-rest-client https://metron.apache.org/ + 2.1.1 @@ -36,6 +37,16 @@ + + org.apache.metron + metron-indexing + ${project.parent.version} + + + org.eclipse.persistence + javax.persistence + ${eclipse.javax.persistence.version} + diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java new file mode 100644 index 0000000000..0ee88502ff --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java @@ -0,0 +1,49 @@ +/** + * 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.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import org.apache.metron.common.utils.JSONUtils; + +@Converter +public class JsonConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(Object savedSearches) { + try { + return JSONUtils.INSTANCE.toJSON(savedSearches, false); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Object convertToEntityAttribute(String savedSearches) { + try { + return JSONUtils.INSTANCE.load(savedSearches, Object.class); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java new file mode 100644 index 0000000000..d3fdcc4133 --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java @@ -0,0 +1,64 @@ +/** + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.apache.metron.rest.converter.JsonConverter; + +@Entity +public class AlertsProfile { + + @Id + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String id; + + @Convert(converter = JsonConverter.class) + private List tableColumns; + + @Convert(converter = JsonConverter.class) + private List savedSearches; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public List getTableColumns() { + return tableColumns; + } + + public void setTableColumns(List tableColumns) { + this.tableColumns = tableColumns; + } + + public List getSavedSearches() { + return savedSearches; + } + + public void setSavedSearches(List savedSearches) { + this.savedSearches = savedSearches; + } +} diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java new file mode 100644 index 0000000000..e225d24227 --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java @@ -0,0 +1,45 @@ +/** + * 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.model; + +import org.apache.metron.indexing.dao.search.SearchRequest; + +import java.util.List; + +public class SavedSearch { + + private String name; + private SearchRequest searchRequest; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SearchRequest getSearchRequest() { + return searchRequest; + } + + public void setSearchRequest(SearchRequest searchRequest) { + this.searchRequest = searchRequest; + } +} diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml index cb8752cb58..f6cf49803a 100644 --- a/metron-interface/metron-rest/pom.xml +++ b/metron-interface/metron-rest/pom.xml @@ -34,6 +34,8 @@ 2.5.0 5.1.40 1.1.1.RELEASE + 4.2.2.RELEASE + 2.6.4 @@ -274,6 +276,22 @@ reflections ${global_reflections_version} + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.hibernate + hibernate-entitymanager + + + + + org.eclipse.persistence + org.eclipse.persistence.jpa + ${eclipse.link.version} + @@ -369,4 +387,62 @@ + + + + + + + + load-time-weaving + + + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.springframework + spring-instrument + ${spring.version} + + + + -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.springframework + spring-instrument + ${spring.version} + + + + ${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar + + + + + + + diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java index 9e32c7abde..ee4cda474d 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java @@ -62,4 +62,7 @@ public class MetronRestConstants { public static final String SEARCH_MAX_RESULTS = "search.max.results"; public static final String INDEX_DAO_IMPL = "index.dao.impl"; + + public static final String SECURITY_ROLE_USER = "USER"; + public static final String SECURITY_ROLE_ADMIN = "ADMIN"; } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java new file mode 100644 index 0000000000..80c9d1ae66 --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java @@ -0,0 +1,51 @@ +/** + * 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.config; + +import java.util.Collections; +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; +import org.springframework.transaction.jta.JtaTransactionManager; + +@Configuration +@EntityScan("org.apache.metron") +public class JpaConfiguration extends JpaBaseConfiguration { + + protected JpaConfiguration(DataSource dataSource, JpaProperties properties, + ObjectProvider jtaTransactionManagerProvider) { + super(dataSource, properties, jtaTransactionManagerProvider); + } + + @Override + protected AbstractJpaVendorAdapter createJpaVendorAdapter() { + return new EclipseLinkJpaVendorAdapter(); + } + + @Override + protected Map getVendorProperties() { + return Collections.singletonMap("eclipselink.weaving", "false"); + } +} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java index 789098a0aa..8310df13ed 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java @@ -17,11 +17,15 @@ */ package org.apache.metron.rest.config; +import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN; +import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_USER; + import org.apache.metron.rest.MetronRestConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -36,6 +40,7 @@ @Configuration @EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) @Controller public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @@ -82,10 +87,10 @@ public void configureJdbc(AuthenticationManagerBuilder auth) throws Exception { if (activeProfiles.contains(MetronRestConstants.DEV_PROFILE) || activeProfiles.contains(MetronRestConstants.TEST_PROFILE)) { auth.jdbcAuthentication().dataSource(dataSource) - .withUser("user").password("password").roles("USER").and() - .withUser("user1").password("password").roles("USER").and() - .withUser("user2").password("password").roles("USER").and() - .withUser("admin").password("password").roles("USER", "ADMIN"); + .withUser("user").password("password").roles(SECURITY_ROLE_USER).and() + .withUser("user1").password("password").roles(SECURITY_ROLE_USER).and() + .withUser("user2").password("password").roles(SECURITY_ROLE_USER).and() + .withUser("admin").password("password").roles(SECURITY_ROLE_USER, SECURITY_ROLE_ADMIN); } else { auth.jdbcAuthentication().dataSource(dataSource); } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java new file mode 100644 index 0000000000..8324652cef --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java @@ -0,0 +1,75 @@ +/** + * 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 static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN; + +import org.apache.metron.rest.RestException; +import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.service.AlertsProfileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/alerts/profile") +public class AlertsProfileController { + + @Autowired + private AlertsProfileService alertsProfileService; + + @RequestMapping(method = RequestMethod.GET) + ResponseEntity get() throws RestException { + AlertsProfile alertsProfile = alertsProfileService.get(); + if (alertsProfile != null) { + return new ResponseEntity<>(alertsProfile, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @Secured({"ROLE_" + SECURITY_ROLE_ADMIN}) + @RequestMapping(value = "/all", method = RequestMethod.GET) + ResponseEntity> findAll() throws RestException { + return new ResponseEntity<>(alertsProfileService.findAll(), HttpStatus.OK); + } + + @RequestMapping(method = RequestMethod.POST) + ResponseEntity save(@RequestBody AlertsProfile alertsProfile) + throws RestException { + AlertsProfile savedAlertsProfile = alertsProfileService.save(alertsProfile); + return new ResponseEntity<>(savedAlertsProfile, HttpStatus.OK); + } + + @Secured({"ROLE_" + SECURITY_ROLE_ADMIN}) + @RequestMapping(value = "/{user}", method = RequestMethod.DELETE) + ResponseEntity delete(@PathVariable String user) throws RestException { + if (alertsProfileService.delete(user)) { + return new ResponseEntity<>(HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } +} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java new file mode 100644 index 0000000000..620ff3ffac --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java @@ -0,0 +1,25 @@ +/** + * 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.repository; + +import org.apache.metron.rest.model.AlertsProfile; +import org.springframework.data.repository.CrudRepository; + +public interface AlertsProfileRepository extends CrudRepository { +} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java new file mode 100644 index 0000000000..ed10f145f9 --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java @@ -0,0 +1,40 @@ +/** + * 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.security; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +public class SecurityUtils { + + /** Returns the username of the currently logged in user. + * + * @return username + */ + public static String getCurrentUser() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String user; + if (principal instanceof UserDetails) { + user = ((UserDetails)principal).getUsername(); + } else { + user = principal.toString(); + } + return user; + } +} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java new file mode 100644 index 0000000000..a1c6804a74 --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java @@ -0,0 +1,32 @@ +/** + * 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.service; + +import org.apache.metron.rest.model.AlertsProfile; + +public interface AlertsProfileService { + + AlertsProfile get(); + + Iterable findAll(); + + AlertsProfile save(AlertsProfile alertsProfile); + + boolean delete(String user); +} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java new file mode 100644 index 0000000000..27661daa25 --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java @@ -0,0 +1,62 @@ +/** + * 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.service.impl; + +import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.repository.AlertsProfileRepository; +import org.apache.metron.rest.security.SecurityUtils; +import org.apache.metron.rest.service.AlertsProfileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.stereotype.Service; + +@Service +public class AlertsProfileServiceImpl implements AlertsProfileService { + + @Autowired + private AlertsProfileRepository alertsProfileRepository; + + @Override + public AlertsProfile get() { + return alertsProfileRepository.findOne(SecurityUtils.getCurrentUser()); + } + + @Override + public Iterable findAll() { + return alertsProfileRepository.findAll(); + } + + @Override + public AlertsProfile save(AlertsProfile alertsProfile) { + String user = SecurityUtils.getCurrentUser(); + alertsProfile.setId(user); + return alertsProfileRepository.save(alertsProfile); + } + + @Override + public boolean delete(String user) { + boolean success = true; + try { + alertsProfileRepository.delete(user); + } catch (EmptyResultDataAccessException e) { + success = false; + } + return success; + } +} diff --git a/metron-interface/metron-rest/src/main/resources/application.yml b/metron-interface/metron-rest/src/main/resources/application.yml index 473d29d195..dfcc2334dd 100644 --- a/metron-interface/metron-rest/src/main/resources/application.yml +++ b/metron-interface/metron-rest/src/main/resources/application.yml @@ -47,4 +47,6 @@ search: index: dao: - impl: org.apache.metron.elasticsearch.dao.ElasticsearchDao \ No newline at end of file + impl: org.apache.metron.elasticsearch.dao.ElasticsearchDao + +spring.jpa.generate-ddl: true \ No newline at end of file diff --git a/metron-interface/metron-rest/src/main/resources/log4j.properties b/metron-interface/metron-rest/src/main/resources/log4j.properties index 492cecf9d7..cb378b4070 100644 --- a/metron-interface/metron-rest/src/main/resources/log4j.properties +++ b/metron-interface/metron-rest/src/main/resources/log4j.properties @@ -10,7 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -log4j.rootLogger=ERROR, stdout +log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c] - %m%n \ No newline at end of file diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java new file mode 100644 index 0000000000..4e05a39060 --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java @@ -0,0 +1,219 @@ +/** + * 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 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.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.adrianwalker.multilinestring.Multiline; +import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.service.AlertsProfileService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +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.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(TEST_PROFILE) +public class AlertsProfileControllerIntegrationTest { + + /** + * { "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 user1ProfileJson; + + + /** + * { "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 user2ProfileJson; + + /** + * { "tableColumns": ["user2_field"], "savedSearches": [] } + */ + @Multiline + public static String testJson; + + /** + * { "sampleData":"1467011157.401 415 127.0.0.1 TCP_MISS/200 337891 GET + * http://www.aliexpress.com/af/shoes.html? - DIRECT/207.109.73.154 text/html", + * "patternLabel":"SQUID" } + */ + @Multiline + public static String missingStatementGrokValidationJson; + + @Autowired + private WebApplicationContext wac; + + @Autowired + private AlertsProfileService alertsProfileService; + + private MockMvc mockMvc; + + private String url = "/api/v1/alerts/profile"; + private String user1 = "user1"; + private String user2 = "user2"; + private String admin = "admin"; + private String password = "password"; + + @Before + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); + } + + @Test + public void testSecurity() throws Exception { + this.mockMvc.perform(get(url)) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(url + "/all")) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(post(url).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(url + "/all").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); + this.mockMvc.perform(delete(url + "/user1").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); + } + + @Test + public void test() throws Exception { + for (AlertsProfile alertsProfile : alertsProfileService.findAll()) { + alertsProfileService.delete(alertsProfile.getId()); + } + + this.mockMvc.perform(get(url).with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url).with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.*", hasSize(0))); + + this.mockMvc.perform(post(url).with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isOk()) + .andExpect(content().json(user1ProfileJson)); + + this.mockMvc.perform(get(url).with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1ProfileJson)); + + this.mockMvc.perform(get(url).with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user1ProfileJson + "]")); + + this.mockMvc.perform(post(url).with(httpBasic(user2, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user2ProfileJson)) + .andExpect(status().isOk()) + .andExpect(content().json(user2ProfileJson)); + + this.mockMvc.perform(get(url).with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1ProfileJson)); + + this.mockMvc.perform(get(url).with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2ProfileJson)); + + this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]")); + + this.mockMvc.perform(delete(url + "/user1").with(httpBasic(admin, password))) + .andExpect(status().isOk()); + + this.mockMvc.perform(delete(url + "/user1").with(httpBasic(admin, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url).with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url).with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2ProfileJson)); + + this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user2ProfileJson + "]")); + + this.mockMvc.perform(delete(url + "/user2").with(httpBasic(admin, password))) + .andExpect(status().isOk()); + + this.mockMvc.perform(get(url).with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url).with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.*", hasSize(0))); + } +} From 9a4281cbd0ea4c382c411bc1f107d523125cb4b7 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 10 Aug 2017 15:28:03 -0500 Subject: [PATCH 02/11] added documentation --- metron-interface/metron-rest/README.md | 39 ++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md index 46a3fc0d5b..fea34b32f0 100644 --- a/metron-interface/metron-rest/README.md +++ b/metron-interface/metron-rest/README.md @@ -81,11 +81,12 @@ These are set in the `/etc/sysconfig/metron` file. ## Database setup -The REST application persists data in a relational database and requires a dedicated database user and database (see https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html for more detail). +The REST application persists data in a relational database and requires a dedicated database user and database (see https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html for more detail). +Spring uses Hibernate as the default ORM framework but another framework is needed becaused Hibernate is not compatible with the Apache 2 license. For this reason Metron uses [EclipseLink](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support). See the [Spring Data JPA - EclipseLink](https://github.com/spring-projects/spring-data-examples/tree/master/jpa/eclipselink) project for an example on how to configure EclipseLink in Spring. ### Development -The REST application comes with embedded database support for development purposes (https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support). +The REST application comes with [embedded database support](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support) for development purposes. For example, edit these variables in `/etc/sysconfig/metron` before starting the application to configure H2: ``` @@ -184,6 +185,10 @@ Request and Response objects are JSON formatted. The JSON schemas are available | | | ---------- | +| [ `GET /api/v1/alerts/profile`](#get-apiv1alertsprofile)| +| [ `GET /api/v1/alerts/profile/all`](#get-apiv1alertsprofileall)| +| [ `DELETE /api/v1/alerts/profile`](#delete-apiv1alertsprofile)| +| [ `POST /api/v1/alerts/profile`](#post-apiv1alertsprofile)| | [ `GET /api/v1/global/config`](#get-apiv1globalconfig)| | [ `DELETE /api/v1/global/config`](#delete-apiv1globalconfig)| | [ `POST /api/v1/global/config`](#post-apiv1globalconfig)| @@ -244,6 +249,36 @@ Request and Response objects are JSON formatted. The JSON schemas are available | [ `GET /api/v1/storm/supervisors`](#get-apiv1stormsupervisors)| | [ `GET /api/v1/user`](#get-apiv1user)| +### `GET /api/v1/alerts/profile` + * Description: Retrieves the current user's alerts profile + * Returns: + * 200 - Alerts profile + * 404 - The current user does not have an alerts profile + +### `GET /api/v1/alerts/profile/all` + * Description: Retrieves all users' alerts profiles. Only users that are part of the "ROLE_ADMIN" role are allowed to get all alerts profiles. + * Returns: + * 200 - List of all alerts profiles + * 403 - The current user does not have permission to get all alerts profiles + +### `DELETE /api/v1/alerts/profile` + * Description: Deletes a user's alerts profile. Only users that are part of the "ROLE_ADMIN" role are allowed to delete user alerts profiles. + * Input: + * user - The user whose prolife will be deleted + * Returns: + * 200 - Alerts profile was deleted + * 403 - The current user does not have permission to delete alerts profiles + * 404 - Alerts profile could not be found + +### `POST /api/v1/alerts/profile` + * Description: Creates or updates the current user's alerts profile + * Input: + * alertsProfile - The alerts profile to be saved + * Returns: + * 200 - Alerts profile updated. Returns saved alerts profile. + * 201 - Alerts profile created. Returns saved alerts profile. + + ### `GET /api/v1/global/config` * Description: Retrieves the current Global Config from Zookeeper * Returns: From 9a2bf6199a8160a82924598213f79df4e26d21aa Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 10 Aug 2017 15:40:12 -0500 Subject: [PATCH 03/11] updated eclipselink plugin and excluded hibernate dependencies --- metron-interface/metron-rest/pom.xml | 143 ++++++++++++++++++--------- 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml index f6cf49803a..b3fadb4b47 100644 --- a/metron-interface/metron-rest/pom.xml +++ b/metron-interface/metron-rest/pom.xml @@ -285,6 +285,18 @@ org.hibernate hibernate-entitymanager + + org.hibernate + hibernate-core + + + org.hibernate + hibernate-commons-annotations + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + @@ -388,61 +400,94 @@ - + + - + - + static-weaving - load-time-weaving + + - - true - + + + com.ethlo.persistence.tools + eclipselink-maven-plugin + 2.6.4.2 + + + process-classes + + weave + + + - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.springframework - spring-instrument - ${spring.version} - - - - -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar - - - + + + org.eclipse.persistence + org.eclipse.persistence.jpa + ${eclipse.link.version} + + - + + + - org.springframework.boot - spring-boot-maven-plugin - - - org.springframework - spring-instrument - ${spring.version} - - - - ${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar - - - - - + + + com.ethlo.eclipselink.tools + http://ethlo.com/maven + + + + + + com.ethlo.eclipselink.tools + http://ethlo.com/maven + + + + + + + + + + load-time-weaving + + + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.springframework + spring-instrument + ${spring.version} + + + + -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar + + + + + - + From 179aeaf33859bf20bf6f9ce4e17b7350a4e23830 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 10 Aug 2017 15:40:30 -0500 Subject: [PATCH 04/11] added dependency licenses --- dependencies_with_url.csv | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dependencies_with_url.csv b/dependencies_with_url.csv index 83078ad0a0..179eeb5cb3 100644 --- a/dependencies_with_url.csv +++ b/dependencies_with_url.csv @@ -112,6 +112,7 @@ com.fasterxml.jackson.core:jackson-core:jar:2.6.6:compile,ASLv2,https://github.c com.fasterxml.jackson.core:jackson-core:jar:2.7.4:compile,ASLv2,https://github.com/FasterXML/jackson-core com.fasterxml.jackson.core:jackson-core:jar:2.8.3:compile,ASLv2,https://github.com/FasterXML/jackson-core com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile,ASLv2,http://wiki.fasterxml.com/JacksonHome +com.fasterxml.jackson.core:jackson-databind:jar:2.4.3:compile,ASLv2,http://github.com/FasterXML/jackson com.fasterxml.jackson.core:jackson-databind:jar:2.7.4:compile,ASLv2,http://github.com/FasterXML/jackson com.fasterxml.jackson.core:jackson-databind:jar:2.8.3:compile,ASLv2,http://github.com/FasterXML/jackson com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.6:compile,ASLv2,http://wiki.fasterxml.com/JacksonForCbor @@ -305,3 +306,10 @@ net.byteseek:byteseek:jar:2.0.3:compile,BSD,https://github.com/nishihatapalmer/b org.springframework.security.kerberos:spring-security-kerberos-client:jar:1.0.1.RELEASE:compile,ASLv2,https://github.com/spring-projects/spring-security-kerberos org.springframework.security.kerberos:spring-security-kerberos-core:jar:1.0.1.RELEASE:compile,ASLv2,https://github.com/spring-projects/spring-security-kerberos org.springframework.kafka:spring-kafka:jar:1.1.1.RELEASE:compile,ASLv2,https://github.com/spring-projects/spring-kafka +org.glassfish:javax.json:jar:1.0.4:compile,Common Development and Distribution License (CDDL) v1.0,https://github.com/javaee/jsonp +org.eclipse.persistence:javax.persistence:jar:2.1.1:compile,EPL 1.0,http://www.eclipse.org/eclipselink +org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink +org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink +org.eclipse.persistence:org.eclipse.persistence.core:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink +org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink +org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink From 0ddceaaeab33ce21d96eb9c1f53cba58d1305f2b Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 10 Aug 2017 15:42:14 -0500 Subject: [PATCH 05/11] added unit test --- .../metron/rest/model/AlertsProfile.java | 25 ++++ .../apache/metron/rest/model/SavedSearch.java | 25 +++- .../controller/AlertsProfileController.java | 37 ++++- .../impl/AlertsProfileServiceImpl.java | 6 +- ...lertsProfileControllerIntegrationTest.java | 13 +- .../impl/AlertsProfileServiceImplTest.java | 127 ++++++++++++++++++ .../indexing/dao/search/SearchRequest.java | 30 +++++ 7 files changed, 253 insertions(+), 10 deletions(-) create mode 100644 metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java index d3fdcc4133..1dcab7f4f2 100644 --- a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java @@ -61,4 +61,29 @@ public List getSavedSearches() { public void setSavedSearches(List savedSearches) { this.savedSearches = savedSearches; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AlertsProfile that = (AlertsProfile) o; + + return id != null ? id.equals(that.id) + : that.id == null && (tableColumns != null ? tableColumns.equals(that.tableColumns) + : that.tableColumns == null && (savedSearches != null ? savedSearches + .equals(that.savedSearches) : that.savedSearches == null)); + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (tableColumns != null ? tableColumns.hashCode() : 0); + result = 31 * result + (savedSearches != null ? savedSearches.hashCode() : 0); + return result; + } } diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java index e225d24227..9b71d06771 100644 --- a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java @@ -20,8 +20,6 @@ import org.apache.metron.indexing.dao.search.SearchRequest; -import java.util.List; - public class SavedSearch { private String name; @@ -42,4 +40,27 @@ public SearchRequest getSearchRequest() { public void setSearchRequest(SearchRequest searchRequest) { this.searchRequest = searchRequest; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SavedSearch that = (SavedSearch) o; + + return name != null ? name.equals(that.name) : that.name == null + && (searchRequest != null ? searchRequest.equals(that.searchRequest) + : that.searchRequest == null); + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (searchRequest != null ? searchRequest.hashCode() : 0); + return result; + } } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java index 8324652cef..eccbd8e90a 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java @@ -20,6 +20,10 @@ import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import org.apache.metron.rest.RestException; import org.apache.metron.rest.model.AlertsProfile; import org.apache.metron.rest.service.AlertsProfileService; @@ -40,6 +44,9 @@ public class AlertsProfileController { @Autowired private AlertsProfileService alertsProfileService; + @ApiOperation(value = "Retrieves the current user's alerts profile") + @ApiResponses(value = {@ApiResponse(message = "Alerts profile", code = 200), + @ApiResponse(message = "The current user does not have an alerts profile", code = 404)}) @RequestMapping(method = RequestMethod.GET) ResponseEntity get() throws RestException { AlertsProfile alertsProfile = alertsProfileService.get(); @@ -51,21 +58,43 @@ ResponseEntity get() throws RestException { } @Secured({"ROLE_" + SECURITY_ROLE_ADMIN}) + @ApiOperation(value = "Retrieves all users' alerts profiles. Only users that are part of " + + "the \"ROLE_ADMIN\" role are allowed to get all alerts profiles.") + @ApiResponses(value = {@ApiResponse(message = "List of all alerts profiles", code = 200), + @ApiResponse(message = + "The current user does not have permission to get all alerts profiles", code = 403)}) @RequestMapping(value = "/all", method = RequestMethod.GET) ResponseEntity> findAll() throws RestException { return new ResponseEntity<>(alertsProfileService.findAll(), HttpStatus.OK); } @RequestMapping(method = RequestMethod.POST) - ResponseEntity save(@RequestBody AlertsProfile alertsProfile) + @ApiOperation(value = "Creates or updates the current user's alerts profile") + @ApiResponses(value = { + @ApiResponse(message = "Alerts profile updated. Returns saved alerts profile.", code = 200), + @ApiResponse(message = "Alerts profile created. Returns saved alerts profile.", code = 201)}) + ResponseEntity save(@ApiParam(name = "alertsProfile", value = + "The alerts profile to be saved", required = true) @RequestBody AlertsProfile alertsProfile) throws RestException { - AlertsProfile savedAlertsProfile = alertsProfileService.save(alertsProfile); - return new ResponseEntity<>(savedAlertsProfile, HttpStatus.OK); + if (alertsProfileService.get() == null) { + return new ResponseEntity<>(alertsProfileService.save(alertsProfile), HttpStatus.CREATED); + } else { + return new ResponseEntity<>(alertsProfileService.save(alertsProfile), HttpStatus.OK); + } } @Secured({"ROLE_" + SECURITY_ROLE_ADMIN}) + @ApiOperation(value = "Deletes a user's alerts profile. Only users that are part of " + + "the \"ROLE_ADMIN\" role are allowed to delete user alerts profiles.") + @ApiResponses(value = {@ApiResponse(message = "Alerts profile was deleted", code = 200), + @ApiResponse(message = "The current user does not have permission to delete alerts profiles", + code = 403), + @ApiResponse(message = "Alerts profile could not be found", code = 404)}) @RequestMapping(value = "/{user}", method = RequestMethod.DELETE) - ResponseEntity delete(@PathVariable String user) throws RestException { + ResponseEntity delete( + @ApiParam(name = "user", value = "The user whose prolife will be deleted", required = true) + @PathVariable String user) + throws RestException { if (alertsProfileService.delete(user)) { return new ResponseEntity<>(HttpStatus.OK); } else { diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java index 27661daa25..a2ec7875a8 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java @@ -29,9 +29,13 @@ @Service public class AlertsProfileServiceImpl implements AlertsProfileService { - @Autowired private AlertsProfileRepository alertsProfileRepository; + @Autowired + public AlertsProfileServiceImpl(AlertsProfileRepository alertsProfileRepository) { + this.alertsProfileRepository = alertsProfileRepository; + } + @Override public AlertsProfile get() { return alertsProfileRepository.findOne(SecurityUtils.getCurrentUser()); diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java index 4e05a39060..ccc69c3c70 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java @@ -52,8 +52,9 @@ public class AlertsProfileControllerIntegrationTest { /** * { "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 } } ] + * "searchRequest": { "from": 0, "indices": ["bro"], "query": "*", "size": 5 } }, + * { "name": "user1 search 2", "searchRequest": { "from": 10, "indices": ["snort"], + * "query": "*", "size": 10 } } ] * } */ @Multiline @@ -135,6 +136,12 @@ public void test() throws Exception { content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(jsonPath("$.*", hasSize(0))); + this.mockMvc.perform(post(url).with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isCreated()) + .andExpect(content().json(user1ProfileJson)); + this.mockMvc.perform(post(url).with(httpBasic(user1, password)).with(csrf()) .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) .content(user1ProfileJson)) @@ -159,7 +166,7 @@ public void test() throws Exception { this.mockMvc.perform(post(url).with(httpBasic(user2, password)).with(csrf()) .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) .content(user2ProfileJson)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(content().json(user2ProfileJson)); this.mockMvc.perform(get(url).with(httpBasic(user1, password))) diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java new file mode 100644 index 0000000000..3030b60d42 --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java @@ -0,0 +1,127 @@ +/* + * 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.service.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Iterator; +import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.repository.AlertsProfileRepository; +import org.apache.metron.rest.service.AlertsProfileService; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +public class AlertsProfileServiceImplTest { + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private AlertsProfileRepository alertsProfileRepository; + private AlertsProfileService alertsProfileService; + private String testUser = "user1"; + + @Before + public void setUp() throws Exception { + alertsProfileRepository = mock(AlertsProfileRepository.class); + alertsProfileService = new AlertsProfileServiceImpl(alertsProfileRepository); + + Authentication authentication = mock(Authentication.class); + UserDetails userDetails = mock(UserDetails.class); + when(authentication.getPrincipal()).thenReturn(userDetails); + when(userDetails.getUsername()).thenReturn(testUser); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + + @Test + public void getShouldProperlyReturnActiveProfile() throws Exception { + AlertsProfile alertsProfile = new AlertsProfile(); + alertsProfile.setId(testUser); + when(alertsProfileRepository.findOne(testUser)).thenReturn(alertsProfile); + + AlertsProfile expectedAlertsProfile = new AlertsProfile(); + expectedAlertsProfile.setId(testUser); + assertEquals(expectedAlertsProfile, alertsProfileService.get()); + verify(alertsProfileRepository, times(1)).findOne(testUser); + verifyNoMoreInteractions(alertsProfileRepository); + } + + @Test + public void findAllShouldProperlyReturnActiveProfiles() throws Exception { + AlertsProfile alertsProfile1 = new AlertsProfile(); + alertsProfile1.setId(testUser); + AlertsProfile alertsProfile2 = new AlertsProfile(); + alertsProfile2.setId(testUser); + when(alertsProfileRepository.findAll()) + .thenReturn(Arrays.asList(alertsProfile1, alertsProfile2)); + + AlertsProfile expectedAlertsProfile1 = new AlertsProfile(); + expectedAlertsProfile1.setId(testUser); + AlertsProfile expectedAlertsProfile2 = new AlertsProfile(); + expectedAlertsProfile2.setId(testUser); + Iterator actualAlertsProfiles = alertsProfileService.findAll().iterator(); + assertEquals(expectedAlertsProfile1, actualAlertsProfiles.next()); + assertEquals(expectedAlertsProfile2, actualAlertsProfiles.next()); + assertFalse(actualAlertsProfiles.hasNext()); + verify(alertsProfileRepository, times(1)).findAll(); + verifyNoMoreInteractions(alertsProfileRepository); + } + + @Test + public void saveShouldProperlySaveActiveProfile() throws Exception { + AlertsProfile savedAlertsProfile = new AlertsProfile(); + savedAlertsProfile.setId(testUser); + when(alertsProfileRepository.save(savedAlertsProfile)).thenReturn(savedAlertsProfile); + + AlertsProfile expectedAlertsProfile = new AlertsProfile(); + expectedAlertsProfile.setId(testUser); + AlertsProfile alertsProfile = new AlertsProfile(); + assertEquals(expectedAlertsProfile, alertsProfileService.save(alertsProfile)); + + verify(alertsProfileRepository, times(1)).save(savedAlertsProfile); + verifyNoMoreInteractions(alertsProfileRepository); + } + + @Test + public void deleteShouldProperlyDeleteActiveProfile() throws Exception { + assertTrue(alertsProfileService.delete(testUser)); + + doThrow(new EmptyResultDataAccessException(1)).when(alertsProfileRepository).delete(testUser); + assertFalse(alertsProfileService.delete(testUser)); + + verify(alertsProfileRepository, times(2)).delete(testUser); + verifyNoMoreInteractions(alertsProfileRepository); + } + + +} diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java index b92b36ded0..5b4ef5a71f 100644 --- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java +++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.metron.indexing.dao.search; import java.util.ArrayList; @@ -86,4 +87,33 @@ public Optional> getFacetFields() { public void setFacetFields(List facetFields) { this.facetFields = facetFields; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SearchRequest that = (SearchRequest) o; + + return indices != null ? indices.equals(that.indices) : that.indices == null + && (query != null ? query.equals(that.query) + : that.query == null) && size == that.size && from == that.from && (sort != null ? sort.equals(that.sort) + : that.sort == null) && (facetFields != null ? facetFields.equals(that.facetFields) + : that.facetFields == null); + } + + @Override + public int hashCode() { + int result = indices != null ? indices.hashCode() : 0; + result = 31 * result + (query != null ? query.hashCode() : 0); + result = 31 * result + getSize(); + result = 31 * result + getFrom(); + result = 31 * result + (sort != null ? sort.hashCode() : 0); + result = 31 * result + (facetFields != null ? facetFields.hashCode() : 0); + return result; + } } From 0a75e79bfa86b7430a54e798cf5ef0c1eab4ab0b Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 10 Aug 2017 17:09:32 -0500 Subject: [PATCH 06/11] reverted log level --- .../metron-rest/src/main/resources/application.yml | 2 +- .../metron-rest/src/main/resources/log4j.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/metron-interface/metron-rest/src/main/resources/application.yml b/metron-interface/metron-rest/src/main/resources/application.yml index dfcc2334dd..bad3560b90 100644 --- a/metron-interface/metron-rest/src/main/resources/application.yml +++ b/metron-interface/metron-rest/src/main/resources/application.yml @@ -49,4 +49,4 @@ index: dao: impl: org.apache.metron.elasticsearch.dao.ElasticsearchDao -spring.jpa.generate-ddl: true \ No newline at end of file +spring.jpa.generate-ddl: true diff --git a/metron-interface/metron-rest/src/main/resources/log4j.properties b/metron-interface/metron-rest/src/main/resources/log4j.properties index cb378b4070..e5f4095df2 100644 --- a/metron-interface/metron-rest/src/main/resources/log4j.properties +++ b/metron-interface/metron-rest/src/main/resources/log4j.properties @@ -10,7 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -log4j.rootLogger=INFO, stdout +log4j.rootLogger=ERROR, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c] - %m%n \ No newline at end of file +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c] - %m%n From b13e0da86b82c8a7c0d1dc560b140e7e1d7378d3 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Fri, 11 Aug 2017 17:23:00 -0500 Subject: [PATCH 07/11] Fixed merge conflicts --- .../apache/metron/rest/config/TestConfig.java | 28 ++++++++-------- .../SearchControllerIntegrationTest.java | 33 +++++++------------ .../UpdateControllerIntegrationTest.java | 32 +++++++----------- 3 files changed, 37 insertions(+), 56 deletions(-) diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java index 9c75f2f5ec..f06ec0d733 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java @@ -17,18 +17,25 @@ */ package org.apache.metron.rest.config; +import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import kafka.admin.AdminUtils$; import kafka.utils.ZKStringSerializer$; import kafka.utils.ZkUtils; import org.I0Itec.zkclient.ZkClient; import org.apache.commons.io.IOUtils; -import org.apache.curator.CuratorZookeeperClient; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; -import org.apache.metron.TestConstants; import org.apache.metron.common.configuration.ConfigurationsUtils; +import org.apache.metron.hbase.mock.MockHBaseTableProvider; import org.apache.metron.integration.ComponentRunner; import org.apache.metron.integration.UnableToStartException; import org.apache.metron.integration.components.KafkaComponent; @@ -36,7 +43,6 @@ import org.apache.metron.rest.mock.MockStormCLIClientWrapper; import org.apache.metron.rest.mock.MockStormRestTemplate; import org.apache.metron.rest.service.impl.StormCLIWrapper; -import org.apache.zookeeper.KeeperException; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -44,22 +50,14 @@ import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.web.client.RestTemplate; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.apache.metron.common.configuration.ConfigurationsUtils.getClient; -import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; - @Configuration @Profile(TEST_PROFILE) public class TestConfig { + static { + MockHBaseTableProvider.addToCache("updates", "t"); + } + @Bean public Properties zkProperties() { return new Properties(); diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java index 1c5310bdd6..2b6dbfb4d1 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java @@ -17,7 +17,18 @@ */ package org.apache.metron.rest.controller; -import org.apache.metron.hbase.mock.MockHBaseTableProvider; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.HashMap; +import java.util.Map; import org.apache.metron.indexing.dao.InMemoryDao; import org.apache.metron.indexing.dao.SearchIntegrationTest; import org.apache.metron.indexing.dao.search.FieldType; @@ -25,7 +36,6 @@ import org.json.simple.parser.ParseException; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -37,20 +47,6 @@ 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.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.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles(TEST_PROFILE) @@ -70,11 +66,6 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { private String user = "user"; private String password = "password"; - @BeforeClass - public static void setupHbase() { - MockHBaseTableProvider.addToCache("updates", "t"); - } - @Before public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UpdateControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UpdateControllerIntegrationTest.java index 0d7fde7225..a3fbe46fd9 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UpdateControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UpdateControllerIntegrationTest.java @@ -17,16 +17,26 @@ */ package org.apache.metron.rest.controller; +import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; +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.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.NavigableMap; import org.adrianwalker.multilinestring.Multiline; import org.apache.curator.framework.CuratorFramework; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Result; -import org.apache.metron.hbase.mock.MockHTable; import org.apache.metron.hbase.mock.MockHBaseTableProvider; +import org.apache.metron.hbase.mock.MockHTable; import org.apache.metron.rest.service.UpdateService; import org.junit.Assert; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -39,18 +49,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import java.util.NavigableMap; - -import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -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.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles(TEST_PROFILE) @@ -112,12 +110,6 @@ public class UpdateControllerIntegrationTest extends DaoControllerTest { @Multiline public static String replace; - - @BeforeClass - public static void setupHbase() { - MockHBaseTableProvider.addToCache(TABLE, CF); - } - @Before public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); From d6b99c4bd9cc90a7d4741c8826674472d92ae057 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 17 Aug 2017 09:48:09 -0500 Subject: [PATCH 08/11] replaced e.printStackTrace to LOG.error --- .../org/apache/metron/rest/converter/JsonConverter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java index 0ee88502ff..1059f5cd19 100644 --- a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java @@ -20,19 +20,23 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.io.IOException; +import java.lang.invoke.MethodHandles; import javax.persistence.AttributeConverter; import javax.persistence.Converter; import org.apache.metron.common.utils.JSONUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Converter public class JsonConverter implements AttributeConverter { + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @Override public String convertToDatabaseColumn(Object savedSearches) { try { return JSONUtils.INSTANCE.toJSON(savedSearches, false); } catch (JsonProcessingException e) { - e.printStackTrace(); + LOG.error("Error converting value to JSON", e); } return null; } @@ -42,7 +46,7 @@ public Object convertToEntityAttribute(String savedSearches) { try { return JSONUtils.INSTANCE.load(savedSearches, Object.class); } catch (IOException e) { - e.printStackTrace(); + LOG.error("Error converting JSON to value", e); } return null; } From 4e8b5f0a19338b93bc6347f85ce1d8a8ec939e13 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 24 Aug 2017 15:11:56 -0500 Subject: [PATCH 09/11] small formatting improvements --- .../metron/rest/model/AlertsProfile.java | 7 +- ...lertsProfileControllerIntegrationTest.java | 80 +++++++++++-------- .../indexing/dao/search/SearchRequest.java | 10 +-- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java index 1dcab7f4f2..3685797279 100644 --- a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java @@ -73,10 +73,9 @@ public boolean equals(Object o) { AlertsProfile that = (AlertsProfile) o; - return id != null ? id.equals(that.id) - : that.id == null && (tableColumns != null ? tableColumns.equals(that.tableColumns) - : that.tableColumns == null && (savedSearches != null ? savedSearches - .equals(that.savedSearches) : that.savedSearches == null)); + return id != null ? id.equals(that.id) : that.id == null && + (tableColumns != null ? tableColumns.equals(that.tableColumns) : that.tableColumns == null && + (savedSearches != null ? savedSearches.equals(that.savedSearches) : that.savedSearches == null)); } @Override diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java index ccc69c3c70..5088ddb579 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java @@ -1,19 +1,16 @@ /** - * 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 + * 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 + * 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. + * 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; @@ -51,10 +48,28 @@ public class AlertsProfileControllerIntegrationTest { /** - * { "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 } } ] + * { + * "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 @@ -62,27 +77,24 @@ public class AlertsProfileControllerIntegrationTest { /** - * { "tableColumns": ["user2_field"], "savedSearches": [ { "name": "user2 search 1", - * "searchRequest": { "from": 0, "indices": ["bro", "snort"], "query": "ip_src_addr:192.168.1.1", - * "size": 100 } } ] } + * { + * "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 user2ProfileJson; - /** - * { "tableColumns": ["user2_field"], "savedSearches": [] } - */ - @Multiline - public static String testJson; - - /** - * { "sampleData":"1467011157.401 415 127.0.0.1 TCP_MISS/200 337891 GET - * http://www.aliexpress.com/af/shoes.html? - DIRECT/207.109.73.154 text/html", - * "patternLabel":"SQUID" } - */ - @Multiline - public static String missingStatementGrokValidationJson; - @Autowired private WebApplicationContext wac; diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java index 12cdd6b002..fb0c47a856 100644 --- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java +++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java @@ -119,11 +119,11 @@ public boolean equals(Object o) { SearchRequest that = (SearchRequest) o; - return indices != null ? indices.equals(that.indices) : that.indices == null - && (query != null ? query.equals(that.query) - : that.query == null) && size == that.size && from == that.from && (sort != null ? sort.equals(that.sort) - : that.sort == null) && (facetFields != null ? facetFields.equals(that.facetFields) - : that.facetFields == null); + return indices != null ? indices.equals(that.indices) : that.indices == null && + (query != null ? query.equals(that.query) : that.query == null) && size == that.size && + from == that.from && + (sort != null ? sort.equals(that.sort) : that.sort == null) && + (facetFields != null ? facetFields.equals(that.facetFields) : that.facetFields == null); } @Override From 8ba1d595ca51bc9b54dcb55e3b45cff224ef5574 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Thu, 31 Aug 2017 15:20:11 -0500 Subject: [PATCH 10/11] added documentation to integration test --- ...lertsProfileControllerIntegrationTest.java | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java index 5088ddb579..0a3d1862dd 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java @@ -111,6 +111,9 @@ public class AlertsProfileControllerIntegrationTest { @Before public void setup() throws Exception { + for (AlertsProfile alertsProfile : alertsProfileService.findAll()) { + alertsProfileService.delete(alertsProfile.getId()); + } this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); } @@ -132,103 +135,153 @@ public void testSecurity() throws Exception { @Test public void test() throws Exception { - for (AlertsProfile alertsProfile : alertsProfileService.findAll()) { - alertsProfileService.delete(alertsProfile.getId()); - } + 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(url).with(httpBasic(user1, password))) .andExpect(status().isNotFound()); + // user2 should get a 404 because an alerts profile has not been created this.mockMvc.perform(get(url).with(httpBasic(user2, password))) .andExpect(status().isNotFound()); + // getting all alerts profiles should return an empty list this.mockMvc.perform(get(url + "/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(url).with(httpBasic(user1, password)).with(csrf()) .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) .content(user1ProfileJson)) .andExpect(status().isCreated()) .andExpect(content().json(user1ProfileJson)); + // user1 updates their alerts profile this.mockMvc.perform(post(url).with(httpBasic(user1, password)).with(csrf()) .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) .content(user1ProfileJson)) .andExpect(status().isOk()) .andExpect(content().json(user1ProfileJson)); + // user1 gets their alerts profile this.mockMvc.perform(get(url).with(httpBasic(user1, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json(user1ProfileJson)); + // user2 alerts profile should still be empty this.mockMvc.perform(get(url).with(httpBasic(user2, password))) .andExpect(status().isNotFound()); + // getting all alerts profiles should only return user1's this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json("[" + user1ProfileJson + "]")); + // user2 creates their alerts profile this.mockMvc.perform(post(url).with(httpBasic(user2, password)).with(csrf()) .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) .content(user2ProfileJson)) .andExpect(status().isCreated()) .andExpect(content().json(user2ProfileJson)); + // user2 updates their alerts profile this.mockMvc.perform(get(url).with(httpBasic(user1, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json(user1ProfileJson)); + // user2 gets their alerts profile this.mockMvc.perform(get(url).with(httpBasic(user2, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json(user2ProfileJson)); + // getting all alerts profiles should return both this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]")); + } + + /** 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(url + "/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(url + "/user1").with(httpBasic(admin, password))) .andExpect(status().isNotFound()); + // user1 should get a 404 when trying to retrieve their alerts profile this.mockMvc.perform(get(url).with(httpBasic(user1, password))) .andExpect(status().isNotFound()); + // user2's alerts profile should still exist this.mockMvc.perform(get(url).with(httpBasic(user2, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json(user2ProfileJson)); + // getting all alerts profiles should only return user2's this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) .andExpect(status().isOk()) .andExpect( content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(content().json("[" + user2ProfileJson + "]")); + // user2 deletes their profile this.mockMvc.perform(delete(url + "/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(url).with(httpBasic(user1, password))) .andExpect(status().isNotFound()); + // user2 should get a 404 when trying to retrieve their alerts profile this.mockMvc.perform(get(url).with(httpBasic(user2, password))) .andExpect(status().isNotFound()); + // getting all alerts profiles should return an empty list this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) .andExpect(status().isOk()) .andExpect( From 435fdc9bf8c0dfd1d6ec1fed56b31bca086e4950 Mon Sep 17 00:00:00 2001 From: merrimanr Date: Tue, 19 Sep 2017 15:54:42 -0500 Subject: [PATCH 11/11] refactored endpoints into single AlertService and AlertController --- .../{AlertsProfile.java => AlertProfile.java} | 4 +- metron-interface/metron-rest/README.md | 16 +- ...leController.java => AlertController.java} | 55 ++-- .../rest/controller/AlertsController.java | 55 ---- ...itory.java => AlertProfileRepository.java} | 4 +- .../metron/rest/service/AlertService.java | 9 + .../rest/service/AlertsProfileService.java | 8 +- .../rest/service/impl/AlertServiceImpl.java | 37 ++- .../impl/AlertsProfileServiceImpl.java | 14 +- .../AlertControllerIntegrationTest.java | 265 +++++++++++++++- ...lertsProfileControllerIntegrationTest.java | 291 ------------------ .../service/impl/AlertServiceImplTest.java | 85 ++++- .../impl/AlertsProfileServiceImplTest.java | 127 -------- 13 files changed, 449 insertions(+), 521 deletions(-) rename metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/{AlertsProfile.java => AlertProfile.java} (97%) rename metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/{AlertsProfileController.java => AlertController.java} (67%) delete mode 100644 metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java rename metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/{AlertsProfileRepository.java => AlertProfileRepository.java} (86%) delete mode 100644 metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java delete mode 100644 metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java similarity index 97% rename from metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java rename to metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java index 3685797279..c126ce77e6 100644 --- a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertsProfile.java +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java @@ -26,7 +26,7 @@ import org.apache.metron.rest.converter.JsonConverter; @Entity -public class AlertsProfile { +public class AlertProfile { @Id @JsonProperty(access = JsonProperty.Access.READ_ONLY) @@ -71,7 +71,7 @@ public boolean equals(Object o) { return false; } - AlertsProfile that = (AlertsProfile) o; + AlertProfile that = (AlertProfile) o; return id != null ? id.equals(that.id) : that.id == null && (tableColumns != null ? tableColumns.equals(that.tableColumns) : that.tableColumns == null && diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md index 2627d6fda0..aaea01e659 100644 --- a/metron-interface/metron-rest/README.md +++ b/metron-interface/metron-rest/README.md @@ -186,10 +186,10 @@ Request and Response objects are JSON formatted. The JSON schemas are available | | | ---------- | | [ `POST /api/v1/alert/escalate`](#get-apiv1alertescalate)| -| [ `GET /api/v1/alerts/profile`](#get-apiv1alertsprofile)| -| [ `GET /api/v1/alerts/profile/all`](#get-apiv1alertsprofileall)| -| [ `DELETE /api/v1/alerts/profile`](#delete-apiv1alertsprofile)| -| [ `POST /api/v1/alerts/profile`](#post-apiv1alertsprofile)| +| [ `GET /api/v1/alert/profile`](#get-apiv1alertprofile)| +| [ `GET /api/v1/alert/profile/all`](#get-apiv1alertprofileall)| +| [ `DELETE /api/v1/alert/profile`](#delete-apiv1alertprofile)| +| [ `POST /api/v1/alert/profile`](#post-apiv1alertprofile)| | [ `GET /api/v1/global/config`](#get-apiv1globalconfig)| | [ `DELETE /api/v1/global/config`](#delete-apiv1globalconfig)| | [ `POST /api/v1/global/config`](#post-apiv1globalconfig)| @@ -264,19 +264,19 @@ Request and Response objects are JSON formatted. The JSON schemas are available * Returns: * 200 - Alerts were escalated -### `GET /api/v1/alerts/profile` +### `GET /api/v1/alert/profile` * Description: Retrieves the current user's alerts profile * Returns: * 200 - Alerts profile * 404 - The current user does not have an alerts profile -### `GET /api/v1/alerts/profile/all` +### `GET /api/v1/alert/profile/all` * Description: Retrieves all users' alerts profiles. Only users that are part of the "ROLE_ADMIN" role are allowed to get all alerts profiles. * Returns: * 200 - List of all alerts profiles * 403 - The current user does not have permission to get all alerts profiles -### `DELETE /api/v1/alerts/profile` +### `DELETE /api/v1/alert/profile` * Description: Deletes a user's alerts profile. Only users that are part of the "ROLE_ADMIN" role are allowed to delete user alerts profiles. * Input: * user - The user whose prolife will be deleted @@ -285,7 +285,7 @@ Request and Response objects are JSON formatted. The JSON schemas are available * 403 - The current user does not have permission to delete alerts profiles * 404 - Alerts profile could not be found -### `POST /api/v1/alerts/profile` +### `POST /api/v1/alert/profile` * Description: Creates or updates the current user's alerts profile * Input: * alertsProfile - The alerts profile to be saved diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java similarity index 67% rename from metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java rename to metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java index eccbd8e90a..d8d54112bc 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsProfileController.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java @@ -15,7 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.metron.rest.controller; import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN; @@ -24,9 +23,11 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import java.util.List; +import java.util.Map; import org.apache.metron.rest.RestException; -import org.apache.metron.rest.model.AlertsProfile; -import org.apache.metron.rest.service.AlertsProfileService; +import org.apache.metron.rest.model.AlertProfile; +import org.apache.metron.rest.service.AlertService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -37,19 +38,33 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +/** + * The API resource that is used for alert-related operations. + */ @RestController -@RequestMapping("/api/v1/alerts/profile") -public class AlertsProfileController { +@RequestMapping("/api/v1/alert") +public class AlertController { + /** + * Service used to interact with alerts. + */ @Autowired - private AlertsProfileService alertsProfileService; + private AlertService alertService; + + @ApiOperation(value = "Escalates a list of alerts by producing it to the Kafka escalate topic") + @ApiResponse(message = "Alerts were escalated", code = 200) + @RequestMapping(value = "/escalate", method = RequestMethod.POST) + ResponseEntity escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List> alerts) throws RestException { + alertService.escalateAlerts(alerts); + return new ResponseEntity<>(HttpStatus.OK); + } @ApiOperation(value = "Retrieves the current user's alerts profile") @ApiResponses(value = {@ApiResponse(message = "Alerts profile", code = 200), @ApiResponse(message = "The current user does not have an alerts profile", code = 404)}) - @RequestMapping(method = RequestMethod.GET) - ResponseEntity get() throws RestException { - AlertsProfile alertsProfile = alertsProfileService.get(); + @RequestMapping(value = "/profile", method = RequestMethod.GET) + ResponseEntity get() throws RestException { + AlertProfile alertsProfile = alertService.getProfile(); if (alertsProfile != null) { return new ResponseEntity<>(alertsProfile, HttpStatus.OK); } else { @@ -63,23 +78,23 @@ ResponseEntity get() throws RestException { @ApiResponses(value = {@ApiResponse(message = "List of all alerts profiles", code = 200), @ApiResponse(message = "The current user does not have permission to get all alerts profiles", code = 403)}) - @RequestMapping(value = "/all", method = RequestMethod.GET) - ResponseEntity> findAll() throws RestException { - return new ResponseEntity<>(alertsProfileService.findAll(), HttpStatus.OK); + @RequestMapping(value = "/profile/all", method = RequestMethod.GET) + ResponseEntity> findAll() throws RestException { + return new ResponseEntity<>(alertService.findAllProfiles(), HttpStatus.OK); } - @RequestMapping(method = RequestMethod.POST) @ApiOperation(value = "Creates or updates the current user's alerts profile") @ApiResponses(value = { @ApiResponse(message = "Alerts profile updated. Returns saved alerts profile.", code = 200), @ApiResponse(message = "Alerts profile created. Returns saved alerts profile.", code = 201)}) - ResponseEntity save(@ApiParam(name = "alertsProfile", value = - "The alerts profile to be saved", required = true) @RequestBody AlertsProfile alertsProfile) + @RequestMapping(value = "/profile", method = RequestMethod.POST) + ResponseEntity save(@ApiParam(name = "alertsProfile", value = + "The alerts profile to be saved", required = true) @RequestBody AlertProfile alertsProfile) throws RestException { - if (alertsProfileService.get() == null) { - return new ResponseEntity<>(alertsProfileService.save(alertsProfile), HttpStatus.CREATED); + if (alertService.getProfile() == null) { + return new ResponseEntity<>(alertService.saveProfile(alertsProfile), HttpStatus.CREATED); } else { - return new ResponseEntity<>(alertsProfileService.save(alertsProfile), HttpStatus.OK); + return new ResponseEntity<>(alertService.saveProfile(alertsProfile), HttpStatus.OK); } } @@ -90,12 +105,12 @@ ResponseEntity save(@ApiParam(name = "alertsProfile", value = @ApiResponse(message = "The current user does not have permission to delete alerts profiles", code = 403), @ApiResponse(message = "Alerts profile could not be found", code = 404)}) - @RequestMapping(value = "/{user}", method = RequestMethod.DELETE) + @RequestMapping(value = "/profile/{user}", method = RequestMethod.DELETE) ResponseEntity delete( @ApiParam(name = "user", value = "The user whose prolife will be deleted", required = true) @PathVariable String user) throws RestException { - if (alertsProfileService.delete(user)) { + if (alertService.deleteProfile(user)) { return new ResponseEntity<>(HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java deleted file mode 100644 index 6f028a105e..0000000000 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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 io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import java.util.List; -import java.util.Map; -import org.apache.metron.rest.RestException; -import org.apache.metron.rest.service.AlertService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** - * The API resource that is used for alert-related operations. - */ -@RestController -@RequestMapping("/api/v1/alert") -public class AlertsController { - - /** - * Service used to interact with alerts. - */ - @Autowired - private AlertService alertService; - - @ApiOperation(value = "Escalates a list of alerts by producing it to the Kafka escalate topic") - @ApiResponse(message = "Alerts were escalated", code = 200) - @RequestMapping(value = "/escalate", method = RequestMethod.POST) - ResponseEntity escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List> alerts) throws RestException { - alertService.escalateAlerts(alerts); - return new ResponseEntity<>(HttpStatus.OK); - } -} diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java similarity index 86% rename from metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java rename to metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java index 620ff3ffac..1466d90995 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertsProfileRepository.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java @@ -18,8 +18,8 @@ package org.apache.metron.rest.repository; -import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.model.AlertProfile; import org.springframework.data.repository.CrudRepository; -public interface AlertsProfileRepository extends CrudRepository { +public interface AlertProfileRepository extends CrudRepository { } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java index 9668b7cf41..a5f244b4bf 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import org.apache.metron.rest.RestException; +import org.apache.metron.rest.model.AlertProfile; /** * This is a set of operations created to interact with alerts. @@ -27,4 +28,12 @@ public interface AlertService { void escalateAlerts(List> alerts) throws RestException; + + AlertProfile getProfile(); + + Iterable findAllProfiles(); + + AlertProfile saveProfile(AlertProfile alertsProfile); + + boolean deleteProfile(String user); } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java index a1c6804a74..84672b6637 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java @@ -18,15 +18,15 @@ package org.apache.metron.rest.service; -import org.apache.metron.rest.model.AlertsProfile; +import org.apache.metron.rest.model.AlertProfile; public interface AlertsProfileService { - AlertsProfile get(); + AlertProfile get(); - Iterable findAll(); + Iterable findAll(); - AlertsProfile save(AlertsProfile alertsProfile); + AlertProfile save(AlertProfile alertsProfile); boolean delete(String user); } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java index 46370ebaf7..73babbea05 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java @@ -23,10 +23,14 @@ import org.apache.metron.common.utils.JSONUtils; import org.apache.metron.rest.MetronRestConstants; import org.apache.metron.rest.RestException; +import org.apache.metron.rest.model.AlertProfile; +import org.apache.metron.rest.repository.AlertProfileRepository; +import org.apache.metron.rest.security.SecurityUtils; import org.apache.metron.rest.service.AlertService; import org.apache.metron.rest.service.KafkaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Service; /** @@ -39,12 +43,15 @@ public class AlertServiceImpl implements AlertService { private Environment environment; private final KafkaService kafkaService; + private AlertProfileRepository alertProfileRepository; @Autowired public AlertServiceImpl(final KafkaService kafkaService, - final Environment environment) { + final Environment environment, + final AlertProfileRepository alertsProfileRepository) { this.kafkaService = kafkaService; this.environment = environment; + this.alertProfileRepository = alertsProfileRepository; } @Override @@ -59,4 +66,32 @@ public void escalateAlerts(List> alerts) throws RestExceptio throw new RestException(e); } } + + @Override + public AlertProfile getProfile() { + return alertProfileRepository.findOne(SecurityUtils.getCurrentUser()); + } + + @Override + public Iterable findAllProfiles() { + return alertProfileRepository.findAll(); + } + + @Override + public AlertProfile saveProfile(AlertProfile alertsProfile) { + String user = SecurityUtils.getCurrentUser(); + alertsProfile.setId(user); + return alertProfileRepository.save(alertsProfile); + } + + @Override + public boolean deleteProfile(String user) { + boolean success = true; + try { + alertProfileRepository.delete(user); + } catch (EmptyResultDataAccessException e) { + success = false; + } + return success; + } } diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java index a2ec7875a8..239dbdca7b 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java @@ -18,8 +18,8 @@ package org.apache.metron.rest.service.impl; -import org.apache.metron.rest.model.AlertsProfile; -import org.apache.metron.rest.repository.AlertsProfileRepository; +import org.apache.metron.rest.model.AlertProfile; +import org.apache.metron.rest.repository.AlertProfileRepository; import org.apache.metron.rest.security.SecurityUtils; import org.apache.metron.rest.service.AlertsProfileService; import org.springframework.beans.factory.annotation.Autowired; @@ -29,25 +29,25 @@ @Service public class AlertsProfileServiceImpl implements AlertsProfileService { - private AlertsProfileRepository alertsProfileRepository; + private AlertProfileRepository alertsProfileRepository; @Autowired - public AlertsProfileServiceImpl(AlertsProfileRepository alertsProfileRepository) { + public AlertsProfileServiceImpl(AlertProfileRepository alertsProfileRepository) { this.alertsProfileRepository = alertsProfileRepository; } @Override - public AlertsProfile get() { + public AlertProfile get() { return alertsProfileRepository.findOne(SecurityUtils.getCurrentUser()); } @Override - public Iterable findAll() { + public Iterable findAll() { return alertsProfileRepository.findAll(); } @Override - public AlertsProfile save(AlertsProfile alertsProfile) { + public AlertProfile save(AlertProfile alertsProfile) { String user = SecurityUtils.getCurrentUser(); alertsProfile.setId(user); return alertsProfileRepository.save(alertsProfile); diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java index be320fc44f..c3a4ac449e 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java @@ -18,13 +18,24 @@ package org.apache.metron.rest.controller; 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.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.model.AlertProfile; +import org.apache.metron.rest.service.AlertService; +import org.apache.metron.rest.service.AlertsProfileService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,17 +68,80 @@ public class AlertControllerIntegrationTest { @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 user1ProfileJson; + + + /** + * { + * "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 user2ProfileJson; + + // 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; + @Autowired + private AlertService alertService; + private MockMvc mockMvc; private String alertUrl = "/api/v1/alert"; - private String user = "user"; + private String user1 = "user1"; + private String user2 = "user2"; + private String admin = "admin"; private String password = "password"; @Before public void setup() throws Exception { + for (AlertProfile alertsProfile : alertService.findAllProfiles()) { + alertService.deleteProfile(alertsProfile.getId()); + } this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); } @@ -75,12 +149,197 @@ public void setup() throws Exception { 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 + "/profile")) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(alertUrl + "/profile/all")) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(post(alertUrl + "/profile").with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); + this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); } @Test - public void test() throws Exception { - this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user,password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts)) + 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 + "/profile").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 + "/profile").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should return an empty list + this.mockMvc.perform(get(alertUrl + "/profile/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 + "/profile").with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isCreated()) + .andExpect(content().json(user1ProfileJson)); + + // user1 updates their alerts profile + this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1ProfileJson)) + .andExpect(status().isOk()) + .andExpect(content().json(user1ProfileJson)); + + // user1 gets their alerts profile + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1ProfileJson)); + + // user2 alerts profile should still be empty + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should only return user1's + this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user1ProfileJson + "]")); + + // user2 creates their alerts profile + this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user2, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user2ProfileJson)) + .andExpect(status().isCreated()) + .andExpect(content().json(user2ProfileJson)); + + // user2 updates their alerts profile + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1ProfileJson)); + + // user2 gets their alerts profile + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2ProfileJson)); + + // getting all alerts profiles should return both + this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]")); + } + + /** 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 + "/profile/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 + "/profile/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 + "/profile").with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + // user2's alerts profile should still exist + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2ProfileJson)); + + // getting all alerts profiles should only return user2's + this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("[" + user2ProfileJson + "]")); + + // user2 deletes their profile + this.mockMvc.perform(delete(alertUrl + "/profile/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 + "/profile").with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + // user2 should get a 404 when trying to retrieve their alerts profile + this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should return an empty list + this.mockMvc.perform(get(alertUrl + "/profile/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(); } } diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java deleted file mode 100644 index 0a3d1862dd..0000000000 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsProfileControllerIntegrationTest.java +++ /dev/null @@ -1,291 +0,0 @@ -/** - * 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 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.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.adrianwalker.multilinestring.Multiline; -import org.apache.metron.rest.model.AlertsProfile; -import org.apache.metron.rest.service.AlertsProfileService; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -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.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles(TEST_PROFILE) -public class AlertsProfileControllerIntegrationTest { - - /** - * { - * "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 user1ProfileJson; - - - /** - * { - * "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 user2ProfileJson; - - @Autowired - private WebApplicationContext wac; - - @Autowired - private AlertsProfileService alertsProfileService; - - private MockMvc mockMvc; - - private String url = "/api/v1/alerts/profile"; - private String user1 = "user1"; - private String user2 = "user2"; - private String admin = "admin"; - private String password = "password"; - - @Before - public void setup() throws Exception { - for (AlertsProfile alertsProfile : alertsProfileService.findAll()) { - alertsProfileService.delete(alertsProfile.getId()); - } - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); - } - - @Test - public void testSecurity() throws Exception { - this.mockMvc.perform(get(url)) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get(url + "/all")) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(post(url).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get(url + "/all").with(httpBasic(user1, password)).with(csrf())) - .andExpect(status().isForbidden()); - this.mockMvc.perform(delete(url + "/user1").with(httpBasic(user1, password)).with(csrf())) - .andExpect(status().isForbidden()); - } - - @Test - public void test() 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(url).with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2 should get a 404 because an alerts profile has not been created - this.mockMvc.perform(get(url).with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should return an empty list - this.mockMvc.perform(get(url + "/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(url).with(httpBasic(user1, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isCreated()) - .andExpect(content().json(user1ProfileJson)); - - // user1 updates their alerts profile - this.mockMvc.perform(post(url).with(httpBasic(user1, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isOk()) - .andExpect(content().json(user1ProfileJson)); - - // user1 gets their alerts profile - this.mockMvc.perform(get(url).with(httpBasic(user1, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user1ProfileJson)); - - // user2 alerts profile should still be empty - this.mockMvc.perform(get(url).with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should only return user1's - this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user1ProfileJson + "]")); - - // user2 creates their alerts profile - this.mockMvc.perform(post(url).with(httpBasic(user2, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user2ProfileJson)) - .andExpect(status().isCreated()) - .andExpect(content().json(user2ProfileJson)); - - // user2 updates their alerts profile - this.mockMvc.perform(get(url).with(httpBasic(user1, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user1ProfileJson)); - - // user2 gets their alerts profile - this.mockMvc.perform(get(url).with(httpBasic(user2, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user2ProfileJson)); - - // getting all alerts profiles should return both - this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]")); - } - - /** 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(url + "/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(url + "/user1").with(httpBasic(admin, password))) - .andExpect(status().isNotFound()); - - // user1 should get a 404 when trying to retrieve their alerts profile - this.mockMvc.perform(get(url).with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2's alerts profile should still exist - this.mockMvc.perform(get(url).with(httpBasic(user2, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user2ProfileJson)); - - // getting all alerts profiles should only return user2's - this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user2ProfileJson + "]")); - - // user2 deletes their profile - this.mockMvc.perform(delete(url + "/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(url).with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2 should get a 404 when trying to retrieve their alerts profile - this.mockMvc.perform(get(url).with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should return an empty list - this.mockMvc.perform(get(url + "/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(jsonPath("$.*", hasSize(0))); - } -} diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java index c55e0a53fb..8bed6b3905 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java @@ -17,35 +17,58 @@ */ package org.apache.metron.rest.service.impl; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.metron.rest.MetronRestConstants; +import org.apache.metron.rest.model.AlertProfile; +import org.apache.metron.rest.repository.AlertProfileRepository; import org.apache.metron.rest.service.AlertService; import org.apache.metron.rest.service.KafkaService; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.core.env.Environment; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; @SuppressWarnings("unchecked") public class AlertServiceImplTest { private KafkaService kafkaService; private Environment environment; + private AlertProfileRepository alertProfileRepository; private AlertService alertService; + private String testUser = "user1"; @SuppressWarnings("unchecked") @Before public void setUp() throws Exception { kafkaService = mock(KafkaService.class); environment = mock(Environment.class); - alertService = new AlertServiceImpl(kafkaService, environment); + alertProfileRepository = Mockito.mock(AlertProfileRepository.class); + alertService = new AlertServiceImpl(kafkaService, environment, alertProfileRepository); + + Authentication authentication = Mockito.mock(Authentication.class); + UserDetails userDetails = Mockito.mock(UserDetails.class); + when(authentication.getPrincipal()).thenReturn(userDetails); + when(userDetails.getUsername()).thenReturn(testUser); + SecurityContextHolder.getContext().setAuthentication(authentication); } @Test @@ -66,4 +89,64 @@ public void produceMessageShouldProperlyProduceMessage() throws Exception { verify(kafkaService).produceMessage("escalation", expectedMessage2); verifyZeroInteractions(kafkaService); } + + @Test + public void getShouldProperlyReturnActiveProfile() throws Exception { + AlertProfile alertsProfile = new AlertProfile(); + alertsProfile.setId(testUser); + when(alertProfileRepository.findOne(testUser)).thenReturn(alertsProfile); + + AlertProfile expectedAlertsProfile = new AlertProfile(); + expectedAlertsProfile.setId(testUser); + assertEquals(expectedAlertsProfile, alertService.getProfile()); + verify(alertProfileRepository, times(1)).findOne(testUser); + verifyNoMoreInteractions(alertProfileRepository); + } + + @Test + public void findAllShouldProperlyReturnActiveProfiles() throws Exception { + AlertProfile alertsProfile1 = new AlertProfile(); + alertsProfile1.setId(testUser); + AlertProfile alertsProfile2 = new AlertProfile(); + alertsProfile2.setId(testUser); + when(alertProfileRepository.findAll()) + .thenReturn(Arrays.asList(alertsProfile1, alertsProfile2)); + + AlertProfile expectedAlertsProfile1 = new AlertProfile(); + expectedAlertsProfile1.setId(testUser); + AlertProfile expectedAlertsProfile2 = new AlertProfile(); + expectedAlertsProfile2.setId(testUser); + Iterator actualAlertsProfiles = alertService.findAllProfiles().iterator(); + assertEquals(expectedAlertsProfile1, actualAlertsProfiles.next()); + assertEquals(expectedAlertsProfile2, actualAlertsProfiles.next()); + assertFalse(actualAlertsProfiles.hasNext()); + verify(alertProfileRepository, times(1)).findAll(); + verifyNoMoreInteractions(alertProfileRepository); + } + + @Test + public void saveShouldProperlySaveActiveProfile() throws Exception { + AlertProfile savedAlertsProfile = new AlertProfile(); + savedAlertsProfile.setId(testUser); + when(alertProfileRepository.save(savedAlertsProfile)).thenReturn(savedAlertsProfile); + + AlertProfile expectedAlertsProfile = new AlertProfile(); + expectedAlertsProfile.setId(testUser); + AlertProfile alertsProfile = new AlertProfile(); + assertEquals(expectedAlertsProfile, alertService.saveProfile(alertsProfile)); + + verify(alertProfileRepository, times(1)).save(savedAlertsProfile); + verifyNoMoreInteractions(alertProfileRepository); + } + + @Test + public void deleteShouldProperlyDeleteActiveProfile() throws Exception { + assertTrue(alertService.deleteProfile(testUser)); + + doThrow(new EmptyResultDataAccessException(1)).when(alertProfileRepository).delete(testUser); + assertFalse(alertService.deleteProfile(testUser)); + + verify(alertProfileRepository, times(2)).delete(testUser); + verifyNoMoreInteractions(alertProfileRepository); + } } diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java deleted file mode 100644 index 3030b60d42..0000000000 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImplTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.service.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.Iterator; -import org.apache.metron.rest.model.AlertsProfile; -import org.apache.metron.rest.repository.AlertsProfileRepository; -import org.apache.metron.rest.service.AlertsProfileService; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; - -public class AlertsProfileServiceImplTest { - @Rule - public final ExpectedException exception = ExpectedException.none(); - - private AlertsProfileRepository alertsProfileRepository; - private AlertsProfileService alertsProfileService; - private String testUser = "user1"; - - @Before - public void setUp() throws Exception { - alertsProfileRepository = mock(AlertsProfileRepository.class); - alertsProfileService = new AlertsProfileServiceImpl(alertsProfileRepository); - - Authentication authentication = mock(Authentication.class); - UserDetails userDetails = mock(UserDetails.class); - when(authentication.getPrincipal()).thenReturn(userDetails); - when(userDetails.getUsername()).thenReturn(testUser); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - - @Test - public void getShouldProperlyReturnActiveProfile() throws Exception { - AlertsProfile alertsProfile = new AlertsProfile(); - alertsProfile.setId(testUser); - when(alertsProfileRepository.findOne(testUser)).thenReturn(alertsProfile); - - AlertsProfile expectedAlertsProfile = new AlertsProfile(); - expectedAlertsProfile.setId(testUser); - assertEquals(expectedAlertsProfile, alertsProfileService.get()); - verify(alertsProfileRepository, times(1)).findOne(testUser); - verifyNoMoreInteractions(alertsProfileRepository); - } - - @Test - public void findAllShouldProperlyReturnActiveProfiles() throws Exception { - AlertsProfile alertsProfile1 = new AlertsProfile(); - alertsProfile1.setId(testUser); - AlertsProfile alertsProfile2 = new AlertsProfile(); - alertsProfile2.setId(testUser); - when(alertsProfileRepository.findAll()) - .thenReturn(Arrays.asList(alertsProfile1, alertsProfile2)); - - AlertsProfile expectedAlertsProfile1 = new AlertsProfile(); - expectedAlertsProfile1.setId(testUser); - AlertsProfile expectedAlertsProfile2 = new AlertsProfile(); - expectedAlertsProfile2.setId(testUser); - Iterator actualAlertsProfiles = alertsProfileService.findAll().iterator(); - assertEquals(expectedAlertsProfile1, actualAlertsProfiles.next()); - assertEquals(expectedAlertsProfile2, actualAlertsProfiles.next()); - assertFalse(actualAlertsProfiles.hasNext()); - verify(alertsProfileRepository, times(1)).findAll(); - verifyNoMoreInteractions(alertsProfileRepository); - } - - @Test - public void saveShouldProperlySaveActiveProfile() throws Exception { - AlertsProfile savedAlertsProfile = new AlertsProfile(); - savedAlertsProfile.setId(testUser); - when(alertsProfileRepository.save(savedAlertsProfile)).thenReturn(savedAlertsProfile); - - AlertsProfile expectedAlertsProfile = new AlertsProfile(); - expectedAlertsProfile.setId(testUser); - AlertsProfile alertsProfile = new AlertsProfile(); - assertEquals(expectedAlertsProfile, alertsProfileService.save(alertsProfile)); - - verify(alertsProfileRepository, times(1)).save(savedAlertsProfile); - verifyNoMoreInteractions(alertsProfileRepository); - } - - @Test - public void deleteShouldProperlyDeleteActiveProfile() throws Exception { - assertTrue(alertsProfileService.delete(testUser)); - - doThrow(new EmptyResultDataAccessException(1)).when(alertsProfileRepository).delete(testUser); - assertFalse(alertsProfileService.delete(testUser)); - - verify(alertsProfileRepository, times(2)).delete(testUser); - verifyNoMoreInteractions(alertsProfileRepository); - } - - -}