You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by rm...@apache.org on 2017/09/29 01:01:50 UTC
metron git commit: METRON-1085 Add REST endpoint to save a user
profile for the Alerts UI (merrimanr) closes apache/metron#694
Repository: metron
Updated Branches:
refs/heads/master e2de1caa4 -> e5d876371
METRON-1085 Add REST endpoint to save a user profile for the Alerts UI (merrimanr) closes apache/metron#694
Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/e5d87637
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/e5d87637
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/e5d87637
Branch: refs/heads/master
Commit: e5d876371e523294598bfdb80a9332fa8917a08f
Parents: e2de1ca
Author: merrimanr <me...@gmail.com>
Authored: Thu Sep 28 20:01:36 2017 -0500
Committer: merrimanr <me...@apache.org>
Committed: Thu Sep 28 20:01:36 2017 -0500
----------------------------------------------------------------------
dependencies_with_url.csv | 8 +
metron-interface/metron-rest-client/pom.xml | 11 +
.../metron/rest/converter/JsonConverter.java | 53 ++++
.../apache/metron/rest/model/AlertProfile.java | 88 ++++++
.../apache/metron/rest/model/SavedSearch.java | 66 +++++
metron-interface/metron-rest/README.md | 39 ++-
metron-interface/metron-rest/pom.xml | 121 +++++++++
.../apache/metron/rest/MetronRestConstants.java | 3 +
.../metron/rest/config/JpaConfiguration.java | 51 ++++
.../metron/rest/config/WebSecurityConfig.java | 13 +-
.../metron/rest/controller/AlertController.java | 119 +++++++++
.../rest/controller/AlertsController.java | 55 ----
.../rest/repository/AlertProfileRepository.java | 25 ++
.../metron/rest/security/SecurityUtils.java | 40 +++
.../metron/rest/service/AlertService.java | 9 +
.../rest/service/AlertsProfileService.java | 32 +++
.../rest/service/impl/AlertServiceImpl.java | 37 ++-
.../service/impl/AlertsProfileServiceImpl.java | 66 +++++
.../src/main/resources/application.yml | 2 +
.../src/main/resources/log4j.properties | 2 +-
.../AlertControllerIntegrationTest.java | 265 ++++++++++++++++++-
.../rest/service/impl/AlertServiceImplTest.java | 85 +++++-
.../indexing/dao/search/SearchRequest.java | 30 +++
23 files changed, 1153 insertions(+), 67 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/dependencies_with_url.csv
----------------------------------------------------------------------
diff --git a/dependencies_with_url.csv b/dependencies_with_url.csv
index b95f25b..f022647 100644
--- a/dependencies_with_url.csv
+++ b/dependencies_with_url.csv
@@ -113,6 +113,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
@@ -309,3 +310,10 @@ org.springframework.security.kerberos:spring-security-kerberos-core:jar:1.0.1.RE
org.springframework.kafka:spring-kafka:jar:1.1.1.RELEASE:compile,ASLv2,https://github.com/spring-projects/spring-kafka
ch.hsr:geohash:jar:1.3.0:compile,ASLv2,https://github.com/kungfoo/geohash-java
org.locationtech.spatial4j:spatial4j:jar:0.6:compile,ASLv2,https://github.com/locationtech/spatial4j
+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
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/pom.xml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/pom.xml b/metron-interface/metron-rest-client/pom.xml
index 21e4ffc..95f1bc2 100644
--- a/metron-interface/metron-rest-client/pom.xml
+++ b/metron-interface/metron-rest-client/pom.xml
@@ -23,6 +23,7 @@
<artifactId>metron-rest-client</artifactId>
<url>https://metron.apache.org/</url>
<properties>
+ <eclipse.javax.persistence.version>2.1.1</eclipse.javax.persistence.version>
</properties>
<dependencies>
<dependency>
@@ -36,6 +37,16 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.apache.metron</groupId>
+ <artifactId>metron-indexing</artifactId>
+ <version>${project.parent.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>javax.persistence</artifactId>
+ <version>${eclipse.javax.persistence.version}</version>
+ </dependency>
</dependencies>
</project>
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java
----------------------------------------------------------------------
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 0000000..1059f5c
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java
@@ -0,0 +1,53 @@
+/**
+ * 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 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<Object, String> {
+ 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) {
+ LOG.error("Error converting value to JSON", e);
+ }
+ return null;
+ }
+
+ @Override
+ public Object convertToEntityAttribute(String savedSearches) {
+ try {
+ return JSONUtils.INSTANCE.load(savedSearches, Object.class);
+ } catch (IOException e) {
+ LOG.error("Error converting JSON to value", e);
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
new file mode 100644
index 0000000..c126ce7
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
@@ -0,0 +1,88 @@
+/**
+ * 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 AlertProfile {
+
+ @Id
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private String id;
+
+ @Convert(converter = JsonConverter.class)
+ private List<String> tableColumns;
+
+ @Convert(converter = JsonConverter.class)
+ private List<SavedSearch> savedSearches;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public List<String> getTableColumns() {
+ return tableColumns;
+ }
+
+ public void setTableColumns(List<String> tableColumns) {
+ this.tableColumns = tableColumns;
+ }
+
+ public List<SavedSearch> getSavedSearches() {
+ return savedSearches;
+ }
+
+ public void setSavedSearches(List<SavedSearch> savedSearches) {
+ this.savedSearches = savedSearches;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AlertProfile that = (AlertProfile) 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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java
----------------------------------------------------------------------
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 0000000..9b71d06
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java
@@ -0,0 +1,66 @@
+/**
+ * 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;
+
+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;
+ }
+
+ @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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/README.md
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md
index 3d8d83b..394f4a8 100644
--- a/metron-interface/metron-rest/README.md
+++ b/metron-interface/metron-rest/README.md
@@ -80,11 +80,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:
```
@@ -196,6 +197,10 @@ Request and Response objects are JSON formatted. The JSON schemas are available
| |
| ---------- |
| [ `POST /api/v1/alert/escalate`](#get-apiv1alertescalate)|
+| [ `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)|
@@ -269,6 +274,36 @@ Request and Response objects are JSON formatted. The JSON schemas are available
* alerts - The alerts to be escalated
* Returns:
* 200 - Alerts were escalated
+
+### `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/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/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
+ * 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/alert/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
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/pom.xml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml
index 71caecc..dcc3b85 100644
--- a/metron-interface/metron-rest/pom.xml
+++ b/metron-interface/metron-rest/pom.xml
@@ -34,6 +34,8 @@
<swagger.version>2.5.0</swagger.version>
<mysql.client.version>5.1.40</mysql.client.version>
<spring-kafka.version>1.1.1.RELEASE</spring-kafka.version>
+ <spring.version>4.2.2.RELEASE</spring.version>
+ <eclipse.link.version>2.6.4</eclipse.link.version>
</properties>
<dependencies>
<dependency>
@@ -290,6 +292,34 @@
<artifactId>reflections</artifactId>
<version>${global_reflections_version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-commons-annotations</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.1-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.jpa</artifactId>
+ <version>${eclipse.link.version}</version>
+ </dependency>
</dependencies>
<dependencyManagement>
@@ -385,4 +415,95 @@
</plugin>
</plugins>
</build>
+
+ <profiles>
+ <profile>
+
+ <!--
+ Sets up the build to run the EclipseLink Maven plugin at compile time and instrument
+ domain types. This will prevent the need for load-time weaving when running the app.
+ -->
+
+ <id>static-weaving</id>
+
+ <build>
+ <plugins>
+
+ <!-- Static weaver for EclipseLink -->
+ <plugin>
+ <groupId>com.ethlo.persistence.tools</groupId>
+ <artifactId>eclipselink-maven-plugin</artifactId>
+ <version>2.6.4.2</version>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>weave</goal>
+ </goals>
+ </execution>
+ </executions>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.jpa</artifactId>
+ <version>${eclipse.link.version}</version>
+ </dependency>
+ </dependencies>
+
+ </plugin>
+ </plugins>
+ </build>
+
+ <repositories>
+ <repository>
+ <id>com.ethlo.eclipselink.tools</id>
+ <url>http://ethlo.com/maven</url>
+ </repository>
+ </repositories>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>com.ethlo.eclipselink.tools</id>
+ <url>http://ethlo.com/maven</url>
+ </pluginRepository>
+ </pluginRepositories>
+
+ </profile>
+
+ <profile>
+
+ <!--
+ Sets up the build to use load-time weaving by configuring the Surefire and Boot
+ plugins to run with Spring's instrumentation agent. They will automatically pick
+ up the EclipseLink weaver and instrument domain types at load time.
+ -->
+
+ <id>load-time-weaving</id>
+
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-instrument</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <argLine>-javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar</argLine>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
</project>
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
----------------------------------------------------------------------
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 b0f553f..0bd0ea3 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 META_DAO_IMPL = "meta.dao.impl";
public static final String META_DAO_SORT = "meta.dao.sort";
+
+ public static final String SECURITY_ROLE_USER = "USER";
+ public static final String SECURITY_ROLE_ADMIN = "ADMIN";
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java
----------------------------------------------------------------------
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 0000000..80c9d1a
--- /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<JtaTransactionManager> jtaTransactionManagerProvider) {
+ super(dataSource, properties, jtaTransactionManagerProvider);
+ }
+
+ @Override
+ protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
+ return new EclipseLinkJpaVendorAdapter();
+ }
+
+ @Override
+ protected Map<String, Object> getVendorProperties() {
+ return Collections.singletonMap("eclipselink.weaving", "false");
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
----------------------------------------------------------------------
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 789098a..8310df1 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 @@ import java.util.List;
@Configuration
@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
@Controller
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@@ -82,10 +87,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
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);
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
new file mode 100644
index 0000000..d8d5411
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
@@ -0,0 +1,119 @@
+/**
+ * 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 io.swagger.annotations.ApiOperation;
+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.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;
+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;
+
+/**
+ * The API resource that is used for alert-related operations.
+ */
+@RestController
+@RequestMapping("/api/v1/alert")
+public class AlertController {
+
+ /**
+ * 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<Void> escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List<Map<String, Object>> 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(value = "/profile", method = RequestMethod.GET)
+ ResponseEntity<AlertProfile> get() throws RestException {
+ AlertProfile alertsProfile = alertService.getProfile();
+ if (alertsProfile != null) {
+ return new ResponseEntity<>(alertsProfile, HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+ }
+
+ @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 = "/profile/all", method = RequestMethod.GET)
+ ResponseEntity<Iterable<AlertProfile>> findAll() throws RestException {
+ return new ResponseEntity<>(alertService.findAllProfiles(), HttpStatus.OK);
+ }
+
+ @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)})
+ @RequestMapping(value = "/profile", method = RequestMethod.POST)
+ ResponseEntity<AlertProfile> save(@ApiParam(name = "alertsProfile", value =
+ "The alerts profile to be saved", required = true) @RequestBody AlertProfile alertsProfile)
+ throws RestException {
+ if (alertService.getProfile() == null) {
+ return new ResponseEntity<>(alertService.saveProfile(alertsProfile), HttpStatus.CREATED);
+ } else {
+ return new ResponseEntity<>(alertService.saveProfile(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 = "/profile/{user}", method = RequestMethod.DELETE)
+ ResponseEntity<Void> delete(
+ @ApiParam(name = "user", value = "The user whose prolife will be deleted", required = true)
+ @PathVariable String user)
+ throws RestException {
+ if (alertService.deleteProfile(user)) {
+ return new ResponseEntity<>(HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java
----------------------------------------------------------------------
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 6f028a1..0000000
--- 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<Void> escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List<Map<String, Object>> alerts) throws RestException {
- alertService.escalateAlerts(alerts);
- return new ResponseEntity<>(HttpStatus.OK);
- }
-}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java
new file mode 100644
index 0000000..1466d90
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.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.AlertProfile;
+import org.springframework.data.repository.CrudRepository;
+
+public interface AlertProfileRepository extends CrudRepository<AlertProfile, String> {
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
----------------------------------------------------------------------
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 0000000..ed10f14
--- /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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java
----------------------------------------------------------------------
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 9668b7c..a5f244b 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 @@ package org.apache.metron.rest.service;
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 @@ import org.apache.metron.rest.RestException;
public interface AlertService {
void escalateAlerts(List<Map<String, Object>> alerts) throws RestException;
+
+ AlertProfile getProfile();
+
+ Iterable<AlertProfile> findAllProfiles();
+
+ AlertProfile saveProfile(AlertProfile alertsProfile);
+
+ boolean deleteProfile(String user);
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java
----------------------------------------------------------------------
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 0000000..84672b6
--- /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.AlertProfile;
+
+public interface AlertsProfileService {
+
+ AlertProfile get();
+
+ Iterable<AlertProfile> findAll();
+
+ AlertProfile save(AlertProfile alertsProfile);
+
+ boolean delete(String user);
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java
----------------------------------------------------------------------
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 46370eb..73babbe 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 java.util.Map;
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 class AlertServiceImpl implements AlertService {
throw new RestException(e);
}
}
+
+ @Override
+ public AlertProfile getProfile() {
+ return alertProfileRepository.findOne(SecurityUtils.getCurrentUser());
+ }
+
+ @Override
+ public Iterable<AlertProfile> 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;
+ }
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java
----------------------------------------------------------------------
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 0000000..239dbdc
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java
@@ -0,0 +1,66 @@
+/**
+ * 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.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;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AlertsProfileServiceImpl implements AlertsProfileService {
+
+ private AlertProfileRepository alertsProfileRepository;
+
+ @Autowired
+ public AlertsProfileServiceImpl(AlertProfileRepository alertsProfileRepository) {
+ this.alertsProfileRepository = alertsProfileRepository;
+ }
+
+ @Override
+ public AlertProfile get() {
+ return alertsProfileRepository.findOne(SecurityUtils.getCurrentUser());
+ }
+
+ @Override
+ public Iterable<AlertProfile> findAll() {
+ return alertsProfileRepository.findAll();
+ }
+
+ @Override
+ public AlertProfile save(AlertProfile 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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/resources/application.yml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/resources/application.yml b/metron-interface/metron-rest/src/main/resources/application.yml
index 764bd40..c3012fa 100644
--- a/metron-interface/metron-rest/src/main/resources/application.yml
+++ b/metron-interface/metron-rest/src/main/resources/application.yml
@@ -58,3 +58,5 @@ meta:
dao:
# By default, we use the ElasticsearchMetaAlertDao
impl: org.apache.metron.elasticsearch.dao.ElasticsearchMetaAlertDao
+
+spring.jpa.generate-ddl: true
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/resources/log4j.properties b/metron-interface/metron-rest/src/main/resources/log4j.properties
index 492cecf..e5f4095 100644
--- a/metron-interface/metron-rest/src/main/resources/log4j.properties
+++ b/metron-interface/metron-rest/src/main/resources/log4j.properties
@@ -13,4 +13,4 @@
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
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
----------------------------------------------------------------------
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 be320fc..c3a4ac4 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 class AlertControllerIntegrationTest {
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();
}
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
----------------------------------------------------------------------
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 c55e0a5..8bed6b3 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 class AlertServiceImplTest {
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<AlertProfile> 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);
+ }
}
http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java
----------------------------------------------------------------------
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 3a72002..f4b6196 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;
@@ -115,4 +116,33 @@ public class SearchRequest {
public void setFacetFields(List<String> 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;
+ }
}