adding Config-editor core and services projects (#15)

* adding config-editor-core project

* cleaning config-editor-core pom file

* adding config-editor-services project

* increase nortem version

* making ServiceAggregatorImpl Builder public

* remove redundant JsonRuleConfigInfoProvider class from centrifuge service
This commit is contained in:
Marian Novotny
2019-10-14 12:34:38 +01:00
committed by GitHub Enterprise
parent 61a5c51ecb
commit 9f68bea6f5
75 changed files with 6235 additions and 26 deletions

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>alerting</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nikita-core</artifactId>

View File

@@ -11,13 +11,13 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>alerting</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-common</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.adrianwalker</groupId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>alerting</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nikita-spark</artifactId>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>alerting</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
@@ -23,7 +23,7 @@
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nikita-core</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>jackson-databind</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>alerting</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nikita-storm</artifactId>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>alerting</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
@@ -70,7 +70,7 @@
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nikita-core</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modules>
<module>nikita-core</module>

View File

@@ -0,0 +1 @@
"# config-editor-core"

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>config-editor-core</artifactId>
<name>config-editor-core</name>
<packaging>jar</packaging>
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>config-editor</artifactId>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>5.2.1.201812262042-r</version>
</dependency>
<dependency>
<groupId>org.eclipse.mylyn.github</groupId>
<artifactId>org.eclipse.egit.github.core</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson_version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson_version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson_version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.adrianwalker</groupId>
<artifactId>multiline-string</artifactId>
<version>0.1.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-Xlint:unchecked</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,7 @@
package uk.co.gresearch.nortem.configeditor.common;
public class AuthorisationException extends RuntimeException {
public AuthorisationException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,11 @@
package uk.co.gresearch.nortem.configeditor.common;
public interface AuthorisationProvider {
enum AuthorisationResult {
UNDEFINED,
ALLOWED,
FORBIDDEN,
}
AuthorisationResult getUserAuthorisation(String user, String serviceName);
}

View File

@@ -0,0 +1,93 @@
package uk.co.gresearch.nortem.configeditor.common;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class ConfigEditorUtils {
private static final String EMPTY_UI_LAYOUT = "{\"layout\": {}}";
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String DOT_SEPARATOR_REGEX = "\\.";
private static final String SCHEMA_FORM_LAYOUT_KEY = "x-schema-form";
private static final String LAYOUT_FIELD_NAME = "layout";
private static final String INDEX_REPLACE_REGEX = "\"minItems\"\\s*:\\s*1";
private static final String INDEX_REPLACEMENT = "\"minItems\":0";
public static Optional<String> readTextFromResources(String filename) {
ClassLoader classLoader = new Object(){}.getClass().getClassLoader();
try (InputStream in = classLoader.getResourceAsStream(filename)) {
int ch;
StringBuilder sb = new StringBuilder();
while ((ch = in.read()) != -1) {
sb.append((char) ch);
}
return Optional.of(sb.toString());
} catch (Exception e) {
LOG.error("could not get file", e);
return Optional.empty();
}
}
public static Optional<String> readUiLayoutFile(String filePath) {
try {
return readTextFromFile(filePath);
} catch (FileNotFoundException ex) {
return Optional.of(EMPTY_UI_LAYOUT);
} catch (IOException ex) {
return Optional.empty();
}
}
private static Optional<String> readTextFromFile(String filePath) throws IOException{
try (FileInputStream fs = new FileInputStream(filePath)) {
int ch;
StringBuilder sb = new StringBuilder();
while ((ch = fs.read()) != -1) {
sb.append((char) ch);
}
return Optional.of(sb.toString());
} catch (FileNotFoundException ex) {
LOG.error("Could not find the file at %s", filePath, ex);
throw ex;
} catch (IOException ex) {
LOG.error("An error occurred while trying to read file %s", filePath, ex);
throw ex;
}
}
public static Optional<String> computeRulesSchema(String rulesSchema, String uiConfig) throws IOException {
JsonNode schemaNode = MAPPER.readTree(rulesSchema);
Map<String, Map<String, Object>> formAttributes = MAPPER.readValue(uiConfig,
new TypeReference<HashMap<String, Map<String, Object>>>() {
});
Set<String> layoutKeys = formAttributes.get(LAYOUT_FIELD_NAME).keySet();
for (String key : layoutKeys) {
String[] tree = key.split(DOT_SEPARATOR_REGEX);
JsonNode nodeRef = schemaNode;
for (String branch : tree) {
nodeRef = nodeRef.findValue(branch);
}
((ObjectNode) nodeRef).putPOJO(SCHEMA_FORM_LAYOUT_KEY, formAttributes.get(LAYOUT_FIELD_NAME).get(key));
}
String uiSchema = MAPPER.writeValueAsString(schemaNode);
//NOTE: we change min items in arrays to 0 so it displays better in the UI
uiSchema = uiSchema.replaceAll(INDEX_REPLACE_REGEX, INDEX_REPLACEMENT);
return Optional.ofNullable(uiSchema);
}
}

View File

@@ -0,0 +1,30 @@
package uk.co.gresearch.nortem.configeditor.common;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
public interface ConfigSchemaService extends HealthCheckable {
String NOT_IMPLEMENTED_MSG = "Not implemented";
String SCHEMA_INIT_ERROR = "Error during computing json schema";
ConfigEditorResult getSchema();
ConfigEditorResult validateConfiguration(String configuration);
ConfigEditorResult validateConfigurations(String configurations);
default Health checkHealth() { return Health.up().build(); };
default ConfigEditorResult getFields() {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR, NOT_IMPLEMENTED_MSG);
}
default ConfigEditorResult testConfiguration(String configuration, String event ) {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR, NOT_IMPLEMENTED_MSG);
}
default ConfigEditorResult testConfigurations(String configurations, String event) {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR, NOT_IMPLEMENTED_MSG);
}
}

View File

@@ -0,0 +1,7 @@
package uk.co.gresearch.nortem.configeditor.common;
import org.springframework.boot.actuate.health.Health;
public interface HealthCheckable {
Health checkHealth();
}

View File

@@ -0,0 +1,82 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import java.util.Map;
public class ConfigInfo {
private Map<String, String> filesContent;
private String commitMessage;
private int oldVersion;
private int version;
private String committer;
private String committerEmail;
private String branchName = "master";
private boolean shouldCleanDirectory = false;
public Map<String, String> getFilesContent() {
return filesContent;
}
public void setFilesContent(Map<String, String> filesContent) {
this.filesContent = filesContent;
}
public String getCommitMessage() {
return commitMessage;
}
public void setCommitMessage(String commitMessage) {
this.commitMessage = commitMessage;
}
public int getOldVersion() {
return oldVersion;
}
public void setOldVersion(int oldVersion) {
this.oldVersion = oldVersion;
}
public String getCommitter() {
return committer;
}
public void setCommitter(String committer) {
this.committer = committer;
}
public String getCommitterEmail() {
return committerEmail;
}
public void setCommitterEmail(String committerEmail) {
this.committerEmail = committerEmail;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public boolean isNewConfig() {
return oldVersion == 0;
}
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
public boolean shouldCleanDirectory() {
return shouldCleanDirectory;
}
public void shouldCleanDirectory(boolean shouldCleanDirectory) {
this.shouldCleanDirectory = shouldCleanDirectory;
}
}

View File

@@ -0,0 +1,55 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.TimeZone;
public interface ConfigInfoProvider {
String RULE_COMMIT_TEMPLATE_NEW = "Adding new rule: %s";
String RULE_COMMIT_TEMPLATE_UPDATE = "Updating rule: %s to version: %d";
String RULE_COMMIT_TEMPLATE_RELEASE = "Rules released to version: %d";
String CONFIG_COMMIT_TEMPLATE_NEW = "Adding new configuration: %s";
String CONFIG_COMMIT_TEMPLATE_UPDATE = "Updating configuration: %s to version: %d";
String CONFIG_COMMIT_TEMPLATE_RELEASE = "Configuration released to version: %d";
String RELEASE_BRANCH_TEMPLATE = "release_%d_by_%s_on_%s";
int MILLI_SECONDS = 1000;
ConfigInfo getConfigInfo(String user, String config);
ConfigInfo getReleaseInfo(String user, String release);
int getReleaseVersion(List<ConfigEditorFile> files);
ConfigEditorFile.ContentType getFileContentType();
default ConfigInfo configInfoFromUser(String userName) {
int delimiter = userName.indexOf('@');
if (delimiter <= 0) {
throw new IllegalArgumentException(
String.format("Unexpected userName format: %s", userName));
}
ConfigInfo ret = new ConfigInfo();
String committer = userName.substring(0, delimiter);
ret.setCommitter(committer);
ret.setCommitterEmail(userName);
return ret;
}
default String getLocalDateTime() {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(System.currentTimeMillis() / MILLI_SECONDS),
TimeZone.getDefault().toZoneId())
.toString()
.replaceAll(":", "-");
}
default boolean isStoreFile(String filename) {
return true;
}
default boolean isReleaseFile(String filename) {
return true;
}
}

View File

@@ -0,0 +1,28 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.common.HealthCheckable;
public interface ConfigStore extends HealthCheckable {
ConfigEditorResult addConfig(String user, String newConfig);
ConfigEditorResult updateConfig(String user, String configToUpdate);
ConfigEditorResult getConfigs();
ConfigEditorResult getConfigsRelease();
ConfigEditorResult getConfigsReleaseStatus();
ConfigEditorResult submitConfigsRelease(String user, String rulesRelease);
ConfigEditorResult getRepositories();
ConfigEditorResult shutDown();
ConfigEditorResult awaitShutDown();
Health checkHealth();
}

View File

@@ -0,0 +1,376 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorRepositories;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import java.io.Closeable;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult.StatusCode.BAD_REQUEST;
import static uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult.StatusCode.OK;
public class ConfigStoreImpl implements ConfigStore, Closeable {
private static final Logger LOG =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final int RELEASES_MAXIMUM_SHUTTING_DOWN_TIME = 60000; //1min
private static final int RULES_MAXIMUM_SHUTTING_DOWN_TIME = 120000; //2min
private final ExecutorService gitStoreService =
Executors.newSingleThreadExecutor();
private final ExecutorService gitReleasesService =
Executors.newSingleThreadExecutor();
private final AtomicReference<List<ConfigEditorFile>> filesCache =
new AtomicReference<>();
private final AtomicReference<Exception> exception =
new AtomicReference<>();
private final GitRepository gitStoreRepo;
private final GitRepository gitReleasesRepo;
private final ReleasePullRequestService pullRequestService;
private final ConfigInfoProvider configInfoProvider;
public ConfigStoreImpl(GitRepository gitStoreRepo,
GitRepository gitReleasesRepo,
ReleasePullRequestService pullRequestService,
ConfigInfoProvider configInfoProvider) throws IOException, GitAPIException {
this.gitStoreRepo = gitStoreRepo;
this.gitReleasesRepo = gitReleasesRepo;
this.pullRequestService = pullRequestService;
this.configInfoProvider = configInfoProvider;
//NOTE: we would like to init rules cache
init();
}
private List<ConfigEditorFile> getFiles(ConfigEditorResult result, Function<String, Boolean> filter) {
List<ConfigEditorFile> ret = result.getAttributes().getFiles();
ret.removeIf(x -> !filter.apply(x.getFileName()));
return ret;
}
private void init() throws IOException, GitAPIException {
ConfigEditorResult result = gitStoreRepo.getFiles();
if (result.getStatusCode() != OK) {
throw new IllegalStateException("Problem during initialisation");
}
List<ConfigEditorFile> files = getFiles(result, configInfoProvider::isStoreFile);
filesCache.set(files);
}
private ConfigEditorResult updateConfigInternally(ConfigInfo configInfo) {
try {
Callable<ConfigEditorResult> command =
() -> gitStoreRepo.transactCopyAndCommit(configInfo);
filesCache.set(getFiles(gitStoreService.submit(command).get(), configInfoProvider::isStoreFile));
return getConfigs();
} catch (Exception e) {
exception.set(e);
String msg = String.format("Exception %s\n during storing a config %s in git",
ExceptionUtils.getStackTrace(e),
configInfo.getFilesContent());
LOG.error(msg);
gitStoreService.shutdown();
return ConfigEditorResult.fromException(e);
}
}
@Override
public ConfigEditorResult addConfig(String user, String newConfig) {
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
LOG.info(String.format("User %s requested to add configuration", user));
ConfigInfo configInfo;
try {
configInfo = configInfoProvider.getConfigInfo(user, newConfig);
Set<String> intersection = filesCache.get().stream().map(x -> x.getFileName()).collect(Collectors.toSet());
intersection.retainAll(configInfo.getFilesContent().keySet());
if (!configInfo.isNewConfig()
|| !intersection.isEmpty()) {
String msg = "Configuration already exists or has wrong version";
LOG.error(msg);
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST,
msg);
}
return updateConfigInternally(configInfo);
} catch (Exception e) {
String msg = String.format("Exception %s\n during adding new configuration %s, user: %s",
ExceptionUtils.getStackTrace(e),
newConfig,
user);
LOG.error(msg);
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST,
msg);
}
}
@Override
public ConfigEditorResult updateConfig(String user, String configToUpdate) {
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
LOG.info(String.format("User %s requested to update the configuration", user));
ConfigInfo configInfo;
try {
configInfo = configInfoProvider.getConfigInfo(user, configToUpdate);
Set<String> intersection = filesCache.get().stream().map(x -> x.getFileName()).collect(Collectors.toSet());
intersection.retainAll(configInfo.getFilesContent().keySet());
if (configInfo.isNewConfig()
|| intersection.isEmpty()) {
String msg = "Rule does not exist or has wrong version";
LOG.error(msg);
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST,
msg);
}
return updateConfigInternally(configInfo);
} catch (Exception e) {
String msg = String.format("Exception %s\n during updating configuration %s, user: %s",
ExceptionUtils.getStackTrace(e),
configToUpdate,
user);
LOG.error(msg);
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST,
msg);
}
}
@Override
public ConfigEditorResult getConfigs() {
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
List<ConfigEditorFile> files = filesCache.get();
if (files == null || files.isEmpty()) {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR,
"Empty rules");
}
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setFiles(files);
return new ConfigEditorResult(OK, attributes);
}
@Override
public ConfigEditorResult getConfigsRelease() {
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
Callable<ConfigEditorResult> command = () -> gitReleasesRepo.getFiles();
try {
ConfigEditorResult ret = gitReleasesService
.submit(command)
.get();
int rulesVersion = configInfoProvider.getReleaseVersion(ret.getAttributes().getFiles());
List<ConfigEditorFile> files = getFiles(ret, configInfoProvider::isReleaseFile);
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setRulesVersion(rulesVersion);
attributes.setFiles(files);
return new ConfigEditorResult(files.isEmpty() ? ConfigEditorResult.StatusCode.ERROR : OK,
attributes);
} catch (Exception e) {
exception.set(e);
String msg = String.format("Exception %s\n during obtaining release files",
ExceptionUtils.getStackTrace(e));
LOG.error(msg);
gitReleasesService.shutdown();
return ConfigEditorResult.fromException(e);
}
}
@Override
public ConfigEditorResult getConfigsReleaseStatus() {
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
try {
return pullRequestService.pendingPullRequest();
} catch (IOException e) {
exception.set(e);
String msg = String.format("Exception %s\n during obtaining pull requests",
ExceptionUtils.getStackTrace(e));
LOG.error(msg);
gitReleasesService.shutdown();
return ConfigEditorResult.fromException(e);
}
}
@Override
public ConfigEditorResult submitConfigsRelease(String user, String rulesRelease) {
LOG.info(String.format("User %s would like submit release", user));
if (exception.get() != null) {
return ConfigEditorResult.fromException(exception.get());
}
ConfigInfo releaseInfo;
try {
releaseInfo = configInfoProvider.getReleaseInfo(user, rulesRelease);
} catch (Exception e) {
String msg = String.format("Exception %s\n processing config info %s, user: %s",
ExceptionUtils.getStackTrace(e),
rulesRelease,
user);
LOG.error(msg);
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST, msg);
}
Callable<ConfigEditorResult> command =
() -> {
ConfigEditorResult pending = pullRequestService.pendingPullRequest();
if (pending.getStatusCode() != OK) {
return pending;
}
if (pending.getAttributes().getPendingPullRequest()) {
return new ConfigEditorResult(BAD_REQUEST, pending.getAttributes());
}
gitReleasesRepo.transactCopyAndCommit(releaseInfo);
return pullRequestService.createPullRequest(releaseInfo);
};
try {
return gitReleasesService
.submit(command)
.get();
} catch (Exception e) {
exception.set(e);
String msg = String.format("Exception %s\n during submitting rule release %s in git",
ExceptionUtils.getStackTrace(e),
rulesRelease);
LOG.error(msg);
gitReleasesService.shutdown();
return ConfigEditorResult.fromException(e);
}
}
@Override
public ConfigEditorResult getRepositories() {
ConfigEditorRepositories repositories = new ConfigEditorRepositories(
gitStoreRepo.getRepoUri(),
gitReleasesRepo.getRepoUri());
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setRulesRepositories(repositories);
return new ConfigEditorResult(OK, attr);
}
@Override
public ConfigEditorResult shutDown() {
LOG.info("Initiating shutting down the config store service");
gitStoreService.shutdown();
gitReleasesService.shutdown();
return new ConfigEditorResult(OK);
}
@Override
public ConfigEditorResult awaitShutDown() {
LOG.info("Initiating awaiting shutting down the config store service");
try {
String errorMsg = "";
if (!gitReleasesService.awaitTermination(RELEASES_MAXIMUM_SHUTTING_DOWN_TIME,
TimeUnit.MILLISECONDS)) {
errorMsg = "Git releases services not shut down in the timeout.";
}
if (!gitStoreService.awaitTermination(RULES_MAXIMUM_SHUTTING_DOWN_TIME,
TimeUnit.MILLISECONDS)) {
errorMsg += "Git store services not shut down in the timeout.";
}
if (!errorMsg.isEmpty()) {
LOG.error(errorMsg);
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR, errorMsg);
}
} catch (InterruptedException e) {
LOG.error(String.format("Exception during shutting down the config store services %s",
ExceptionUtils.getStackTrace(e)));
return ConfigEditorResult.fromException(e);
}
LOG.info("Shutting down the rule store services completed");
return new ConfigEditorResult(OK);
}
@Override
public Health checkHealth() {
Exception e = exception.get();
return e == null
? Health.up().build()
: Health.down(e).build();
}
public static ConfigStore createRuleStore(ConfigStoreProperties props,
ConfigInfoProvider ruleInfoProvider) throws GitAPIException, IOException {
LOG.info(String.format("Initialising git config store service for repositories: %s, %s",
props.getStoreRepositoryName(),
props.getReleaseRepositoryName()));
GitRepository gitStoreRepo = new GitRepository.Builder()
.gitUrl(props.getGithubUrl())
.repoFolder(props.getStoreRepositoryPath())
.repoName(props.getStoreRepositoryName())
.rulesDirectory(props.getStoreDirectory())
.credentials(props.getGitUserName(), props.getGitPassword())
.contentType(ruleInfoProvider.getFileContentType())
.build();
GitRepository gitReleasesRepo = new GitRepository.Builder()
.gitUrl(props.getGithubUrl())
.repoFolder(props.getReleaseRepositoryPath())
.repoName(props.getReleaseRepositoryName())
.rulesDirectory(props.getReleaseDirectory())
.credentials(props.getGitUserName(), props.getGitPassword())
.contentType(ruleInfoProvider.getFileContentType())
.build();
ReleasePullRequestService pullRequestService = new ReleasePullRequestService.Builder()
.uri(props.getGithubUrl())
.repoName(props.getReleaseRepositoryName())
.credentials(props.getGitUserName(), props.getGitPassword())
.build();
LOG.info("Initialising git config store service completed");
return new ConfigStoreImpl(gitStoreRepo,
gitReleasesRepo,
pullRequestService,
ruleInfoProvider);
}
@Override
public void close() {
gitStoreRepo.close();
gitReleasesRepo.close();
}
}

View File

@@ -0,0 +1,85 @@
package uk.co.gresearch.nortem.configeditor.configstore;
public class ConfigStoreProperties {
private String githubUrl;
private String gitUserName;
private String gitPassword;
private String storeRepositoryName;
private String releaseRepositoryName;
private String storeRepositoryPath;
private String releaseRepositoryPath;
private String storeDirectory;
private String releaseDirectory;
public String getGithubUrl() {
return githubUrl;
}
public void setGithubUrl(String githubUrl) {
this.githubUrl = githubUrl;
}
public String getGitUserName() {
return gitUserName;
}
public void setGitUserName(String gitUserName) {
this.gitUserName = gitUserName;
}
public String getGitPassword() {
return gitPassword;
}
public void setGitPassword(String gitPassword) {
this.gitPassword = gitPassword;
}
public String getStoreRepositoryName() {
return storeRepositoryName;
}
public void setStoreRepositoryName(String storeRepositoryName) {
this.storeRepositoryName = storeRepositoryName;
}
public String getReleaseRepositoryName() {
return releaseRepositoryName;
}
public void setReleaseRepositoryName(String releaseRepositoryName) {
this.releaseRepositoryName = releaseRepositoryName;
}
public String getStoreRepositoryPath() {
return storeRepositoryPath;
}
public void setStoreRepositoryPath(String storeRepositoryPath) {
this.storeRepositoryPath = storeRepositoryPath;
}
public String getReleaseRepositoryPath() {
return releaseRepositoryPath;
}
public void setReleaseRepositoryPath(String releaseRepositoryPath) {
this.releaseRepositoryPath = releaseRepositoryPath;
}
public String getStoreDirectory() {
return storeDirectory;
}
public void setStoreDirectory(String storeDirectory) {
this.storeDirectory = storeDirectory;
}
public String getReleaseDirectory() {
return releaseDirectory;
}
public void setReleaseDirectory(String releaseDirectory) {
this.releaseDirectory = releaseDirectory;
}
}

View File

@@ -0,0 +1,242 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFileHistoryItem;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
public class GitRepository implements Closeable {
public static final String MAIN_BRANCH = "master";
private final CredentialsProvider credentialsProvider;
private final Git git;
private final Path rulesPath;
private final String repoUri;
private final ConfigEditorFile.ContentType contentType;
private static String readFile(Path path) throws IOException {
return new String(Files.readAllBytes(path), UTF_8);
}
private GitRepository(Builder builder) {
credentialsProvider = builder.credentialsProvider;
git = builder.git;
rulesPath = builder.rulesPath;
repoUri = builder.repoUri;
contentType = builder.contentType;
}
public ConfigEditorResult transactCopyAndCommit(
ConfigInfo ruleInfo) throws GitAPIException, IOException {
git.pull()
.setCredentialsProvider(credentialsProvider)
.call();
if (!MAIN_BRANCH.equals(ruleInfo.getBranchName())) {
git.branchCreate().setName(ruleInfo.getBranchName()).call();
git.checkout().setName(ruleInfo.getBranchName()).call();
}
if (ruleInfo.shouldCleanDirectory()) {
FileUtils.cleanDirectory(rulesPath.toFile());
}
for (Map.Entry<String, String> file : ruleInfo.getFilesContent().entrySet()) {
Path filePath = Paths.get(rulesPath.toString(), file.getKey());
Files.write(filePath, file.getValue().getBytes());
}
git.add()
.addFilepattern(rulesPath.getFileName().toString())
.call();
git.commit()
.setAll(true)
.setAuthor(ruleInfo.getCommitter(), ruleInfo.getCommitterEmail())
.setMessage(ruleInfo.getCommitMessage())
.call();
git.push()
.setCredentialsProvider(credentialsProvider)
.call();
if (!MAIN_BRANCH.equals(ruleInfo.getBranchName())) {
ConfigEditorResult branchFiles = getFiles();
git.checkout()
.setName(MAIN_BRANCH)
.call();
return branchFiles;
}
return getFiles();
}
public ConfigEditorResult getFiles() throws IOException, GitAPIException {
git.pull()
.setCredentialsProvider(credentialsProvider)
.call();
Map<String, ConfigEditorFile> files = new HashMap<>();
try (Stream<Path> paths = Files.walk(rulesPath)) {
paths.filter(Files::isRegularFile)
.forEach(x -> {
try {
files.put(x.getFileName().toString(),
new ConfigEditorFile(x.getFileName().toString(), readFile(x), contentType));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
Iterable<RevCommit> commits = git.log().setRevFilter(RevFilter.NO_MERGES).call();
try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
df.setRepository(git.getRepository());
df.setDiffComparator(RawTextComparator.DEFAULT);
for (RevCommit commit : commits) {
if (commit.getParentCount() == 0) {
//NOTE: we skip init commit
continue;
}
String author = commit.getAuthorIdent().getName();
int commitTime = commit.getCommitTime();
RevCommit parent = commit.getParent(0);
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
for (DiffEntry diff : diffs) {
int linesAdded = 0, linesRemoved = 0;
int lastSlashIndex = diff.getNewPath().lastIndexOf('/');
String fileName = lastSlashIndex < 0
? diff.getNewPath()
: diff.getNewPath().substring(lastSlashIndex + 1);
if (!files.containsKey(fileName)) {
continue;
}
for (Edit edit : df.toFileHeader(diff).toEditList()) {
linesRemoved += edit.getEndA() - edit.getBeginA();
linesAdded += edit.getEndB() - edit.getBeginB();
}
ConfigEditorFileHistoryItem historyItem = new ConfigEditorFileHistoryItem();
historyItem.setAuthor(author);
historyItem.setTimestamp(commitTime);
historyItem.setAddedLines(linesAdded);
historyItem.setRemoved(linesRemoved);
files.get(fileName).getFileHistory().add(historyItem);
}
}
}
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setFiles(files.values().stream().collect(Collectors.toList()));
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
}
public String getRepoUri() {
return repoUri;
}
@Override
public void close() {
git.close();
}
public static class Builder {
private static final String GIT_REPO_URL_FORMAT = "%s/%s.git";
private String repoName;
private String repoUri;
private String gitUrl;
private String repoFolder;
private Path rulesPath;
private String rulesDirectory = "";
private CredentialsProvider credentialsProvider;
private Git git;
private ConfigEditorFile.ContentType contentType = ConfigEditorFile.ContentType.RAW_JSON_STRING;
Builder repoName(String repoName) {
this.repoName = repoName;
return this;
}
Builder gitUrl(String gitUrl) {
this.gitUrl = gitUrl;
return this;
}
Builder rulesDirectory(String directory) {
this.rulesDirectory = directory;
return this;
}
Builder repoFolder(String repoFolder) {
this.repoFolder = repoFolder;
return this;
}
Builder contentType(ConfigEditorFile.ContentType contentType) {
this.contentType = contentType;
return this;
}
Builder credentials(String userName, String password) {
credentialsProvider = new UsernamePasswordCredentialsProvider(userName, password);
return this;
}
GitRepository build() throws GitAPIException, IOException {
if (repoName == null
|| gitUrl == null
|| repoFolder == null
|| credentialsProvider == null) {
throw new IllegalArgumentException("Not provided required properties");
}
File repoFolderDir = new File(repoFolder);
if (repoFolderDir.exists()) {
FileUtils.cleanDirectory(repoFolderDir);
} else {
repoFolderDir.mkdir();
}
repoUri = String.format(GIT_REPO_URL_FORMAT, gitUrl, repoName);
git = Git.cloneRepository()
.setCredentialsProvider(credentialsProvider)
.setURI(repoUri)
.setDirectory(repoFolderDir)
.call();
rulesPath = Paths.get(repoFolder, rulesDirectory);
if (git == null || !repoFolderDir.exists()) {
throw new IllegalStateException("Error during git rules repo initialisation");
}
return new GitRepository(this);
}
}
}

View File

@@ -0,0 +1,266 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JsonConfigInfoProvider implements ConfigInfoProvider {
private static final ObjectReader JSON_READER = new ObjectMapper()
.readerFor(new TypeReference<Map<String, Object>>() { });
private static final String WRONG_RELEASE_FORMAT = "Wrong config release json file format";
private static final String WRONG_CONFIG_FORMAT = "Wrong config json file format";
private static final String MISSING_FILENAME_MSG = "Missing filename: %s";
private static final String WRONG_FILENAME_MSG = "Wrong config name: %s";
private final String configNameField;
private final String configAuthorField;
private final String configVersionField;
private final String configsVersionField;
private final String configFilenameFormat;
private final String releaseFilename;
private final String jsonFileSuffix;
private final String ruleVersionRegex;
private final String releaseVersionRegex;
private final String ruleAuthorRegex;
private final String ruleVersionFormat;
private final String ruleAuthorFormat;
private final String releaseVersionFormat;
private final String commitTemplateNew;
private final String commitTemplateUpdate;
private final String commitTemplateRelease;
private final Pattern ruleNamePattern;
JsonConfigInfoProvider(Builder builder) {
this.configNameField = builder.configNameField;
this.configAuthorField = builder.configAuthorField;
this.configVersionField = builder.configVersionField;
this.configsVersionField = builder.configsVersionField;
this.configFilenameFormat = builder.configFilenameFormat;
this.releaseFilename = builder.releaseFilename;
this.jsonFileSuffix = builder.jsonFileSuffix;
this.ruleVersionRegex = builder.ruleVersionRegex;
this.releaseVersionRegex = builder.releaseVersionRegex;
this.ruleAuthorRegex = builder.ruleAuthorRegex;
this.ruleVersionFormat = builder.ruleVersionFormat;
this.ruleAuthorFormat = builder.ruleAuthorFormat;
this.releaseVersionFormat = builder.releaseVersionFormat;
this.ruleNamePattern = builder.ruleNamePattern;
this.commitTemplateNew = builder.commitTemplateNew;
this.commitTemplateUpdate = builder.commitTemplateUpdate;
this.commitTemplateRelease = builder.commitTemplateRelease;
}
@Override
public ConfigInfo getConfigInfo(String userName, String config) {
ConfigInfo configInfo = configInfoFromUser(userName);
Map<String, Object> metadata;
try {
metadata = JSON_READER.readValue(config);
} catch (IOException e) {
throw new IllegalArgumentException(WRONG_CONFIG_FORMAT);
}
if (metadata == null
|| !(metadata.get(configVersionField) instanceof Number)
|| !(metadata.get(configAuthorField) instanceof String)
|| !(metadata.get(configNameField) instanceof String)) {
throw new IllegalArgumentException(WRONG_CONFIG_FORMAT);
}
String configName = (String)metadata.get(configNameField);
String configAuthor = (String)metadata.get(configAuthorField);
int configVersion = ((Number)metadata.get(configVersionField)).intValue();
Matcher nameMatcher = ruleNamePattern.matcher(configName);
if (!nameMatcher.matches()) {
throw new IllegalArgumentException(
String.format(WRONG_FILENAME_MSG, configName));
}
int newConfigVersion = configVersion + 1;
configInfo.setOldVersion(configVersion);
configInfo.setVersion(newConfigVersion);
String commitMsg = configVersion == 0
? String.format(commitTemplateNew, configName)
: String.format(commitTemplateUpdate, configName, newConfigVersion);
configInfo.setCommitMessage(commitMsg);
Map<String, String> files = new HashMap<>();
String updatedRule = config.replaceFirst(ruleVersionRegex,
String.format(ruleVersionFormat, newConfigVersion));
if (!configAuthor.equals(configInfo.getCommitter())) {
//NOTE: we consider author to be the last committer,
// auth logic can be added here when needed
updatedRule = updatedRule.replaceFirst(ruleAuthorRegex,
String.format(ruleAuthorFormat, configInfo.getCommitter()));
}
files.put(String.format(configFilenameFormat, configName), updatedRule);
configInfo.setFilesContent(files);
return configInfo;
}
@Override
public ConfigInfo getReleaseInfo(String userName, String release) {
ConfigInfo configInfo = configInfoFromUser(userName);
int releaseVersion = getReleaseVersion(release);
int newRulesVersion = releaseVersion + 1;
configInfo.setVersion(newRulesVersion);
configInfo.setOldVersion(releaseVersion);
configInfo.setBranchName(String.format(RELEASE_BRANCH_TEMPLATE,
newRulesVersion,
configInfo.getCommitter(),
getLocalDateTime()));
configInfo.setCommitMessage(String.format(commitTemplateRelease, newRulesVersion));
String updatedRelease = release.replaceFirst(releaseVersionRegex,
String.format(releaseVersionFormat, newRulesVersion));
Map<String, String> files = new HashMap<>();
files.put(releaseFilename, updatedRelease);
configInfo.setFilesContent(files);
return configInfo;
}
private int getReleaseVersion(String content) {
Map<String, Object> metadata;
try {
metadata = JSON_READER.readValue(content);
} catch (IOException e) {
throw new IllegalArgumentException(WRONG_RELEASE_FORMAT);
}
if (metadata == null
|| !(metadata.get(configsVersionField) instanceof Number)) {
throw new IllegalArgumentException(WRONG_RELEASE_FORMAT);
}
return ((Number)metadata.get(configsVersionField)).intValue();
}
@Override
public int getReleaseVersion(List<ConfigEditorFile> files) {
Optional<ConfigEditorFile> release = files
.stream()
.filter(x -> x.getFileName().equals(releaseFilename))
.findFirst();
if (!release.isPresent()) {
throw new IllegalArgumentException(String.format(MISSING_FILENAME_MSG, releaseFilename));
}
return getReleaseVersion(release.get().getContent());
}
@Override
public ConfigEditorFile.ContentType getFileContentType() {
return ConfigEditorFile.ContentType.RAW_JSON_STRING;
}
@Override
public boolean isStoreFile(String filename) {
return filename.endsWith(jsonFileSuffix);
}
@Override
public boolean isReleaseFile(String filename) {
return releaseFilename.equals(filename);
}
public static class Builder {
private static final String MISSING_ARGUMENTS = "Missing required argument for the builder";
private static final String CONFIG_VERSION_REGEX_MSG = "\"%s\"\\s*:\\s*\\d+";
private static final String RELEASE_VERSION_REGEX_MSG = "\"%s\"\\s*:\\s*\\d+";
private static final String CONFIG_AUTHOR_REGEX_MSG = "\"%s\"\\s*:\\s*\"\\w+\"";
private static final String CONFIG_VERSION_FORMAT_MSG = "\"%s\": %%d";
private static final String CONFIG_AUTHOR_FORMAT_MSG = "\"%s\": \"%%s\"";
private static final String RELEASE_VERSION_FORMAT = "\"%s\": %%d";
private String configNameField;
private String configAuthorField;
private String configVersionField;
private String configsVersionField;
private String configFilenameFormat = "%s.json";
private String releaseFilename = "rules.json";
private String jsonFileSuffix = "json";
private String ruleVersionRegex;
private String releaseVersionRegex;
private String ruleAuthorRegex;
private String ruleVersionFormat;
private String ruleAuthorFormat;
private String releaseVersionFormat;
private Pattern ruleNamePattern = Pattern.compile("^[a-zA-Z0-9_]+$");
private String commitTemplateNew = RULE_COMMIT_TEMPLATE_NEW;
private String commitTemplateUpdate = RULE_COMMIT_TEMPLATE_UPDATE;
private String commitTemplateRelease = RULE_COMMIT_TEMPLATE_RELEASE;
public Builder configNameField(String configNameField) {
this.configNameField = configNameField;
return this;
}
public Builder configAuthorField(String configAuthorField) {
this.configAuthorField = configAuthorField;
return this;
}
public Builder configVersionField(String configVersionField) {
this.configVersionField = configVersionField;
return this;
}
public Builder configsVersionField(String configsVersionField) {
this.configsVersionField = configsVersionField;
return this;
}
public Builder configFilenameFormat(String configFilenameFormat) {
this.configFilenameFormat = configFilenameFormat;
return this;
}
public Builder releaseFilename(String releaseFilename) {
this.releaseFilename = releaseFilename;
return this;
}
public Builder useConfigWordingInGitMessages() {
commitTemplateNew = CONFIG_COMMIT_TEMPLATE_NEW;
commitTemplateUpdate = CONFIG_COMMIT_TEMPLATE_UPDATE;
commitTemplateRelease = CONFIG_COMMIT_TEMPLATE_RELEASE;
return this;
}
public JsonConfigInfoProvider build() {
if (configNameField == null
|| configAuthorField == null
|| configVersionField == null
|| configsVersionField == null
|| configFilenameFormat == null
|| releaseFilename == null
|| jsonFileSuffix == null) {
throw new IllegalArgumentException(MISSING_ARGUMENTS);
}
ruleVersionRegex = String.format(CONFIG_VERSION_REGEX_MSG, configVersionField);
ruleVersionFormat = String.format(CONFIG_VERSION_FORMAT_MSG, configVersionField);
releaseVersionRegex = String.format(RELEASE_VERSION_REGEX_MSG, configsVersionField);
ruleAuthorRegex = String.format(CONFIG_AUTHOR_REGEX_MSG, configAuthorField);
ruleAuthorFormat = String.format(CONFIG_AUTHOR_FORMAT_MSG, configAuthorField);
releaseVersionFormat = String.format(RELEASE_VERSION_FORMAT, configsVersionField);
return new JsonConfigInfoProvider(this);
}
}
}

View File

@@ -0,0 +1,17 @@
package uk.co.gresearch.nortem.configeditor.configstore;
public class JsonRuleConfigInfoProvider {
private static final String AUTHOR_FIELD = "rule_author";
private static final String NAME_FIELD = "rule_name";
private static final String VERSION_FIELD = "rule_version";
private static final String RELEASE_VERSION_FIELD = "rules_version";
public static ConfigInfoProvider create() {
return new JsonConfigInfoProvider.Builder()
.configAuthorField(AUTHOR_FIELD)
.configNameField(NAME_FIELD)
.configsVersionField(RELEASE_VERSION_FIELD)
.configVersionField(VERSION_FIELD)
.build();
}
}

View File

@@ -0,0 +1,98 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.eclipse.egit.github.core.PullRequest;
import org.eclipse.egit.github.core.PullRequestMarker;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.PullRequestService;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import java.io.IOException;
import java.util.List;
public class ReleasePullRequestService {
private static final String BODY_TEMPLATE = "User %s would like to release rules version %d.";
private static final String PR_STATE_OPEN = "open";
private final RepositoryId repoId;
private final PullRequestService service;
private final String branchTo;
private ReleasePullRequestService(Builder builder) {
this.repoId = builder.repoId;
this.service = builder.service;
this.branchTo = builder.branchTo;
}
public ConfigEditorResult createPullRequest(ConfigInfo info) throws IOException {
PullRequest request = new PullRequest();
request.setBody(String.format(BODY_TEMPLATE,
info.getCommitter(),
info.getVersion()));
request.setTitle(info.getCommitMessage());
request.setHead(new PullRequestMarker().setLabel(info.getBranchName()));
request.setBase(new PullRequestMarker().setLabel(branchTo));
PullRequest response = service.createPullRequest(repoId, request);
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setPullRequestUrl(response.getHtmlUrl());
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attributes);
}
public ConfigEditorResult pendingPullRequest() throws IOException {
List<PullRequest> requests = service.getPullRequests(repoId, PR_STATE_OPEN);
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setPendingPullRequest(!requests.isEmpty());
if (!requests.isEmpty()) {
attributes.setPullRequestUrl(requests.get(0).getHtmlUrl());
}
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attributes);
}
public static class Builder {
private String uri;
private String user;
private String password;
private String repoName;
private RepositoryId repoId;
private PullRequestService service;
private String branchTo = GitRepository.MAIN_BRANCH;
public Builder uri(String uri) {
this.uri = uri;
return this;
}
public Builder repoName(String repoName) {
this.repoName = repoName;
return this;
}
public Builder credentials(String user, String password) {
this.user = user;
this.password = password;
return this;
}
public ReleasePullRequestService build() {
if (uri == null
|| user == null
|| repoName == null
|| password == null
|| repoName == null) {
throw new IllegalArgumentException("Missing required parameters");
}
repoId = RepositoryId.createFromId(repoName);
GitHubClient client = GitHubClient.createClient(uri);
client.setCredentials(user, password);
service = new PullRequestService(client);
return new ReleasePullRequestService(this);
}
}
}

View File

@@ -0,0 +1,188 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ConfigEditorAttributes {
private String exception;
private String message;
@JsonProperty("rules_schema")
@JsonRawValue
private String rulesSchema;
@JsonProperty("pull_request_pending")
private Boolean pendingPullRequest;
@JsonProperty("pull_request_url")
private String pullRequestUrl;
@JsonProperty("user_name")
private String userName;
private List<ConfigEditorFile> files;
@JsonProperty("rules_version")
private Integer rulesVersion;
@JsonProperty("rules_repositories")
ConfigEditorRepositories rulesRepositories;
@JsonProperty("fields")
@JsonRawValue
private String fields;
@JsonProperty("template_fields")
private List<TemplateField> templateFields;
@JsonProperty("sensor_template_fields")
private List<SensorTemplateFields> sensorTemplateFields;
@JsonProperty("test_result_output")
private String testResultOutput;
@JsonProperty("test_result_complete")
private Boolean testResultComplete;
private String event;
private List<ConfigEditorService> services;
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getRulesSchema() {
return rulesSchema;
}
public void setRulesSchema(String rulesSchema) {
this.rulesSchema = rulesSchema;
}
public List<ConfigEditorFile> getFiles() {
return files;
}
public void setFiles(List<ConfigEditorFile> files) {
this.files = files;
}
public Boolean getPendingPullRequest() {
return pendingPullRequest;
}
public void setPendingPullRequest(Boolean pendingPullRequest) {
this.pendingPullRequest = pendingPullRequest;
}
public String getPullRequestUrl() {
return pullRequestUrl;
}
public void setPullRequestUrl(String pullRequestUrl) {
this.pullRequestUrl = pullRequestUrl;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getRulesVersion() {
return rulesVersion;
}
public void setRulesVersion(Integer rulesVersion) {
this.rulesVersion = rulesVersion;
}
public ConfigEditorRepositories getRulesRepositories() {
return rulesRepositories;
}
public void setRulesRepositories(ConfigEditorRepositories rulesRepositories) {
this.rulesRepositories = rulesRepositories;
}
public List<TemplateField> getTemplateFields() {
return templateFields;
}
public void setTemplateFields(List<TemplateField> templateFields) {
this.templateFields = templateFields;
}
public List<SensorTemplateFields> getSensorTemplateFields() {
return sensorTemplateFields;
}
public void setSensorTemplateFields(List<SensorTemplateFields> sensorTemplateFields) {
this.sensorTemplateFields = sensorTemplateFields;
}
public String getFields() {
return fields;
}
public void setFields(String fields) {
this.fields = fields;
}
public String getTestResultOutput() {
return testResultOutput;
}
public void setTestResultOutput(String testResultOutput) {
this.testResultOutput = testResultOutput;
}
public Boolean getTestResultComplete() {
return testResultComplete;
}
public void setTestResultComplete(Boolean testResultComplete) {
this.testResultComplete = testResultComplete;
}
public String getEvent() {
return event;
}
@JsonSetter("event")
public void setEvent(JsonNode event) {
this.event = event.toString();
}
public void setEvent(String event) {
this.event = event;
}
public List<ConfigEditorService> getServices() {
return services;
}
public void setServices(List<ConfigEditorService> services) {
this.services = services;
}
}

View File

@@ -0,0 +1,80 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.JsonNode;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ConfigEditorFile {
public enum ContentType {
RAW_JSON_STRING,
STRING
}
@JsonProperty("file_name")
@NotNull
private String fileName;
@JsonRawValue
private String content;
private String stringContent;
@JsonProperty("file_history")
private List<ConfigEditorFileHistoryItem> fileHistory = new ArrayList<>();
public ConfigEditorFile() {
}
public ConfigEditorFile(String fileName, String content, ContentType type) {
this.fileName = fileName;
switch (type) {
case RAW_JSON_STRING:
this.content = content;
break;
case STRING:
this.stringContent = content;
break;
}
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getContent() {
return content;
}
@JsonSetter("content")
void setContent(JsonNode content) {
this.content = content.toString();
}
@JsonProperty("string_content")
public String getStringContent() {
return stringContent;
}
@JsonSetter("string_content")
public void setStringContent(String stringContent) {
this.stringContent = stringContent;
}
@JsonIgnore
public String getContentValue() {
return content != null ? content : stringContent;
}
public List<ConfigEditorFileHistoryItem> getFileHistory() {
return fileHistory;
}
public void setFileHistory(List<ConfigEditorFileHistoryItem> fileHistory) {
this.fileHistory = fileHistory;
}
}

View File

@@ -0,0 +1,64 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.TimeZone;
public class ConfigEditorFileHistoryItem {
private String author;
private String date;
@JsonProperty("added")
private Integer addedLines;
@JsonProperty("removed")
private Integer removedLines;
@JsonIgnore
private Integer timestamp;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public Integer getAddedLines() {
return addedLines;
}
public void setAddedLines(Integer addedLines) {
this.addedLines = addedLines;
}
public Integer getRemoved() {
return removedLines;
}
public void setRemoved(Integer removed) {
this.removedLines = removed;
}
@JsonIgnore
public Integer getTimestamp() {
return timestamp;
}
public void setTimestamp(Integer timestamp) {
this.timestamp = timestamp;
date = LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp),
TimeZone.getDefault().toZoneId()).toString();
}
}

View File

@@ -0,0 +1,31 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ConfigEditorRepositories {
@JsonProperty("rule_store_url")
private String ruleStoreUrl;
@JsonProperty("rules_release_url")
private String rulesReleaseUrl;
public ConfigEditorRepositories(String ruleStoreUrl, String rulesReleaseUrl) {
this.ruleStoreUrl = ruleStoreUrl;
this.rulesReleaseUrl = rulesReleaseUrl;
}
public String getRuleStoreUrl() {
return ruleStoreUrl;
}
public void setRuleStoreUrl(String ruleStoreUrl) {
this.ruleStoreUrl = ruleStoreUrl;
}
public String getRulesReleaseUrl() {
return rulesReleaseUrl;
}
public void setRulesReleaseUrl(String rulesReleaseUrl) {
this.rulesReleaseUrl = rulesReleaseUrl;
}
}

View File

@@ -0,0 +1,55 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.exception.ExceptionUtils;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ConfigEditorResult {
public enum StatusCode {
OK,
BAD_REQUEST,
UNAUTHORISED,
ERROR,
}
@JsonProperty("status_code")
private final StatusCode statusCode;
private final ConfigEditorAttributes attributes;
public ConfigEditorResult(StatusCode statusCode) {
this(statusCode, null);
}
public ConfigEditorResult(StatusCode statusCode, ConfigEditorAttributes attributes) {
this.statusCode = statusCode;
this.attributes = attributes;
}
public StatusCode getStatusCode() {
return statusCode;
}
public ConfigEditorAttributes getAttributes() {
return attributes;
}
public static ConfigEditorResult fromMessage(StatusCode statusCode,
String message) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setMessage(message);
return new ConfigEditorResult(statusCode, attr);
}
public static ConfigEditorResult fromException(Exception e) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setException(ExceptionUtils.getStackTrace(e));
return new ConfigEditorResult(StatusCode.ERROR, attr);
}
public static ConfigEditorResult fromSchema(String schema) {
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setRulesSchema(schema);
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK,
attributes);
}
}

View File

@@ -0,0 +1,13 @@
package uk.co.gresearch.nortem.configeditor.model;
public class ConfigEditorService {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,32 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class SensorTemplateFields {
@JsonProperty("sensor_name")
private String sensorName;
private List<TemplateField> fields;
public SensorTemplateFields(String sensorName, List<TemplateField> fields) {
this.sensorName = sensorName;
this.fields = fields;
}
public String getSensorName() {
return sensorName;
}
public void setSensorName(String sensorName) {
this.sensorName = sensorName;
}
public List<TemplateField> getFields() {
return fields;
}
public void setFields(List<TemplateField> fields) {
this.fields = fields;
}
}

View File

@@ -0,0 +1,25 @@
package uk.co.gresearch.nortem.configeditor.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class TemplateField {
private String name;
public TemplateField(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonIgnore
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,27 @@
package uk.co.gresearch.nortem.configeditor.serviceaggregator;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorService;
import uk.co.gresearch.nortem.configeditor.common.ConfigSchemaService;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigStore;
import java.util.List;
public interface ServiceAggregator {
String UNSUPPORTED_USER_PRINCIPAL_FORMAT = "Unsupported principal format in the authentication structure";
ConfigStore getConfigStore(String user, String serviceName);
ConfigSchemaService getConfigSchema(String user, String serviceName);
List<ConfigStore> getConfigStoreServices();
List<ConfigSchemaService> getConfigSchemaServices();
List<ConfigEditorService> getConfigEditorServices(String user);
Health checkConfigStoreServicesHealth();
Health checkConfigSchemaServicesHealth();
}

View File

@@ -0,0 +1,127 @@
package uk.co.gresearch.nortem.configeditor.serviceaggregator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import uk.co.gresearch.nortem.configeditor.common.*;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigStore;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorService;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class ServiceAggregatorImpl implements ServiceAggregator {
private static final String UNSUPPORTED_SERVICE_MSG = "Unsupported service %s";
private static final String AUTHORISATION_MSG = "User %s is unauthorised to access the service %s";
private final AuthorisationProvider authProvider;
private final Map<String, ConfigStore> configStores;
private final Map<String, ConfigSchemaService> configSchemaServices;
ServiceAggregatorImpl(Builder builder) {
this.authProvider = builder.authProvider;
this.configStores = builder.configStores;
this.configSchemaServices = builder.configSchemaServices;
}
@Override
public ConfigStore getConfigStore(String user, String serviceName) {
return getService(user, serviceName, configStores);
}
@Override
public ConfigSchemaService getConfigSchema(String user, String serviceName) {
return getService(user, serviceName, configSchemaServices);
}
@Override
public List<ConfigStore> getConfigStoreServices() {
return new ArrayList<>(configStores.values());
}
@Override
public List<ConfigSchemaService> getConfigSchemaServices() {
return new ArrayList<>(configSchemaServices.values());
}
@Override
public List<ConfigEditorService> getConfigEditorServices(String user) {
List<ConfigEditorService> ret = new ArrayList<>();
for (String serviceName : configSchemaServices.keySet()) {
try {
ConfigSchemaService current = getConfigSchema(user, serviceName);
ConfigEditorService configEditorService = new ConfigEditorService();
configEditorService.setName(serviceName);
ret.add(configEditorService);
} catch (AuthorisationException e) {
continue;
}
}
return ret;
}
@Override
public Health checkConfigStoreServicesHealth() {
return checkServiceHealth(configStores);
}
@Override
public Health checkConfigSchemaServicesHealth() {
return checkServiceHealth(configSchemaServices);
}
private <T> T getService(String user, String serviceName, Map<String, T> serviceMap) {
if (!serviceMap.containsKey(serviceName)) {
throw new UnsupportedOperationException(String.format(UNSUPPORTED_SERVICE_MSG, serviceName));
}
AuthorisationProvider.AuthorisationResult authResult = authProvider.getUserAuthorisation(user, serviceName);
if (authResult == AuthorisationProvider.AuthorisationResult.FORBIDDEN) {
throw new AuthorisationException(String.format(AUTHORISATION_MSG, user, serviceName));
}
return serviceMap.get(serviceName);
}
private <T extends HealthCheckable> Health checkServiceHealth(Map<String, T> serviceMap) {
for (String name : serviceMap.keySet()) {
Health current = serviceMap.get(name).checkHealth();
try {
if (current.getStatus() != Status.UP) {
return current;
}
} catch (Exception e) {
new Health.Builder().down(e).build();
}
}
return new Health.Builder().up().build();
}
public static class Builder {
private static final String SERVICE_ALREADY_REGISTERED = "Service is already registered";
private static final String NO_SERVICE_REGISTERED = "No services registered in aggregator";
private final AuthorisationProvider authProvider;
private final Map<String, ConfigStore> configStores = new HashMap<>();
private final Map<String, ConfigSchemaService> configSchemaServices = new HashMap<>();
public Builder(AuthorisationProvider authProvider) {
this.authProvider = authProvider;
}
public Builder addService(String name, ConfigStore store, ConfigSchemaService schemaService) {
if (configStores.containsKey(name)) {
throw new IllegalArgumentException(SERVICE_ALREADY_REGISTERED);
}
configStores.put(name, store);
configSchemaServices.put(name, schemaService);
return this;
}
public ServiceAggregator build() {
if (configStores.isEmpty()) {
throw new IllegalArgumentException(NO_SERVICE_REGISTERED);
}
return new ServiceAggregatorImpl(this);
}
}
}

View File

@@ -0,0 +1,268 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ConfigStoreImplTest {
private GitRepository gitRulesRepo;
private GitRepository gitReleasesRepo;
private ReleasePullRequestService pullRequestService;
private ConfigInfoProvider ruleInfoProvider;
private ConfigStoreImpl ruleStore;
private Map<String, String> filesContent = new HashMap<>();
private List<ConfigEditorFile> files;
private ConfigEditorResult getFilesResult;
private ConfigInfo ruleInfo = new ConfigInfo();
@Before
public void setUp() throws IOException, GitAPIException {
gitRulesRepo = Mockito.mock(GitRepository.class);
gitReleasesRepo = Mockito.mock(GitRepository.class);
pullRequestService = Mockito.mock(ReleasePullRequestService.class);
ruleInfoProvider = Mockito.mock(ConfigInfoProvider.class);
when(ruleInfoProvider.getConfigInfo(any(), any())).thenReturn(ruleInfo);
when(ruleInfoProvider.getReleaseInfo(any(), any())).thenReturn(ruleInfo);
when(ruleInfoProvider.isReleaseFile(any())).thenReturn(true);
when(ruleInfoProvider.isStoreFile(any())).thenReturn(true);
when(ruleInfoProvider.getFileContentType()).thenReturn(ConfigEditorFile.ContentType.STRING);
filesContent.put("File.json", "DUMMY_CONTENT");
files = new ArrayList<>();
files.add(new ConfigEditorFile("File.json",
"DUMMY_CONTENT",
ConfigEditorFile.ContentType.RAW_JSON_STRING));
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setFiles(files);
getFilesResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
when(gitRulesRepo.getFiles()).thenReturn(getFilesResult);
when(gitReleasesRepo.getFiles()).thenReturn(getFilesResult);
when(gitRulesRepo.transactCopyAndCommit(any())).thenReturn(getFilesResult);
ruleStore = new ConfigStoreImpl(gitRulesRepo,
gitReleasesRepo,
pullRequestService,
ruleInfoProvider);
}
@After
public void tearDown() {
ruleStore.close();
}
@Test
public void AddRuleOK() throws GitAPIException, IOException {
ruleInfo.setOldVersion(0);
ruleInfo.setFilesContent(new HashMap<>());
ConfigEditorResult ret = ruleStore.addConfig("john", "NEW");
verify(ruleInfoProvider).getConfigInfo("john", "NEW");
verify(gitRulesRepo).transactCopyAndCommit(ruleInfo);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(1, ret.getAttributes().getFiles().size());
Assert.assertEquals("File.json", ret.getAttributes().getFiles().get(0).getFileName());
Assert.assertEquals("DUMMY_CONTENT", ret.getAttributes().getFiles().get(0).getContentValue());
}
@Test
public void AddRuleNotNew() {
ruleInfo.setOldVersion(1);
ruleInfo.setFilesContent(new HashMap<>());
ConfigEditorResult ret = ruleStore.addConfig("john", "NEW");
verify(ruleInfoProvider).getConfigInfo("john", "NEW");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getMessage().contains("wrong version"));
}
@Test
public void AddRuleExisting() {
ruleInfo.setOldVersion(0);
ruleInfo.setFilesContent(filesContent);
ConfigEditorResult ret = ruleStore.addConfig("john", "NEW");
verify(ruleInfoProvider).getConfigInfo("john", "NEW");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getMessage().contains("already exists"));
}
@Test
public void UpdateRuleOK() throws GitAPIException, IOException {
ruleInfo.setOldVersion(1);
ruleInfo.setFilesContent(filesContent);
ConfigEditorResult ret = ruleStore.updateConfig("john", "UPDATE");
verify(ruleInfoProvider).getConfigInfo("john", "UPDATE");
verify(gitRulesRepo).transactCopyAndCommit(ruleInfo);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(1, ret.getAttributes().getFiles().size());
Assert.assertEquals("File.json", ret.getAttributes().getFiles().get(0).getFileName());
Assert.assertEquals("DUMMY_CONTENT", ret.getAttributes().getFiles().get(0).getContentValue());
}
@Test
public void UpdateNew() {
ruleInfo.setOldVersion(0);
ruleInfo.setFilesContent(filesContent);
ConfigEditorResult ret = ruleStore.updateConfig("john", "UPDATE");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getMessage().contains("wrong version"));
}
@Test
public void UpdateNotExist() {
ruleInfo.setOldVersion(0);
ruleInfo.setFilesContent(new HashMap<>());
ConfigEditorResult ret = ruleStore.updateConfig("john", "NEW");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getMessage().contains("does not exist"));
}
@Test
public void getRules() {
ConfigEditorResult ret = ruleStore.getConfigs();
verify(ruleInfoProvider).isStoreFile("File.json");
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(1, ret.getAttributes().getFiles().size());
Assert.assertEquals("File.json", ret.getAttributes().getFiles().get(0).getFileName());
Assert.assertEquals("DUMMY_CONTENT", ret.getAttributes().getFiles().get(0).getContentValue());
}
@Test
public void getRulesFiltered() throws IOException, GitAPIException {
when(ruleInfoProvider.isStoreFile(any())).thenReturn(false);
ruleStore = new ConfigStoreImpl(gitRulesRepo,
gitReleasesRepo,
pullRequestService,
ruleInfoProvider);
ConfigEditorResult ret = ruleStore.getConfigs();
verify(ruleInfoProvider, times(2)).isStoreFile("File.json");
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
}
@Test
public void getRulesRelease() throws IOException, GitAPIException {
when(ruleInfoProvider.getReleaseVersion(any())).thenReturn(1234);
ConfigEditorResult ret = ruleStore.getConfigsRelease();
verify(gitReleasesRepo).getFiles();
verify(ruleInfoProvider).isReleaseFile("File.json");
verify(ruleInfoProvider).getReleaseVersion(files);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(1, ret.getAttributes().getFiles().size());
Assert.assertEquals("File.json", ret.getAttributes().getFiles().get(0).getFileName());
Assert.assertEquals("DUMMY_CONTENT", ret.getAttributes().getFiles().get(0).getContentValue());
Assert.assertEquals(1234, ret.getAttributes().getRulesVersion().intValue());
}
@Test
public void getRulesReleaseFiltered() throws IOException, GitAPIException {
when(ruleInfoProvider.isReleaseFile(any())).thenReturn(false);
ConfigEditorResult ret = ruleStore.getConfigsRelease();
verify(gitReleasesRepo).getFiles();
verify(ruleInfoProvider).isReleaseFile("File.json");
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
}
@Test
public void getRulesReleaseStatusPending() throws IOException {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setPendingPullRequest(true);
attr.setPullRequestUrl("DUMMY_URL");
ConfigEditorResult pullRequestResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
when(pullRequestService.pendingPullRequest()).thenReturn(pullRequestResult);
ConfigEditorResult ret = ruleStore.getConfigsReleaseStatus();
verify(pullRequestService).pendingPullRequest();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getPendingPullRequest());
Assert.assertEquals("DUMMY_URL", ret.getAttributes().getPullRequestUrl());
}
@Test
public void getRulesReleaseStatusNoPullRequest() throws IOException {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setPendingPullRequest(false);
ConfigEditorResult pullRequestResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
when(pullRequestService.pendingPullRequest()).thenReturn(pullRequestResult);
ConfigEditorResult ret = ruleStore.getConfigsReleaseStatus();
verify(pullRequestService).pendingPullRequest();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertFalse(ret.getAttributes().getPendingPullRequest());
}
@Test
public void submitRulesRelease() throws IOException, GitAPIException {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setPendingPullRequest(true);
attr.setPullRequestUrl("DUMMY_URL");
ConfigEditorResult pullRequestResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
attr.setPendingPullRequest(false);
ConfigEditorResult pendingPullRequestResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
when(pullRequestService.createPullRequest(ruleInfo)).thenReturn(pullRequestResult);
when(pullRequestService.pendingPullRequest()).thenReturn(pendingPullRequestResult);
ConfigEditorResult ret = ruleStore.submitConfigsRelease("test", "dummy_rules");
verify(ruleInfoProvider).getReleaseInfo("test", "dummy_rules");
verify(gitReleasesRepo).transactCopyAndCommit(ruleInfo);
verify(pullRequestService).createPullRequest(ruleInfo);
verify(pullRequestService).pendingPullRequest();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals("DUMMY_URL", ret.getAttributes().getPullRequestUrl());
}
@Test
public void submitRulesReleasePending() throws IOException {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
attr.setPendingPullRequest(true);
attr.setPullRequestUrl("DUMMY_URL");
ConfigEditorResult pendingPullRequestResult = new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
when(pullRequestService.pendingPullRequest()).thenReturn(pendingPullRequestResult);
ConfigEditorResult ret = ruleStore.submitConfigsRelease("test", "dummy_rules");
verify(ruleInfoProvider).getReleaseInfo("test", "dummy_rules");
verify(pullRequestService).pendingPullRequest();
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getPendingPullRequest());
Assert.assertEquals("DUMMY_URL", ret.getAttributes().getPullRequestUrl());
}
@Test
public void getRepositoriesTest() {
when(gitRulesRepo.getRepoUri()).thenReturn("RULES_URI");
when(gitReleasesRepo.getRepoUri()).thenReturn("RELEASE_URI");
ConfigEditorResult ret = ruleStore.getRepositories();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertNotNull(ret.getAttributes().getRulesRepositories());
Assert.assertEquals("RULES_URI", ret.getAttributes()
.getRulesRepositories().getRuleStoreUrl());
Assert.assertEquals("RELEASE_URI",
ret.getAttributes().getRulesRepositories().getRulesReleaseUrl());
}
}

View File

@@ -0,0 +1,188 @@
package uk.co.gresearch.nortem.configeditor.configstore;
import org.adrianwalker.multilinestring.Multiline;
import org.junit.Assert;
import org.junit.Test;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import java.util.ArrayList;
import java.util.List;
public class JsonRuleConfigInfoProviderTest {
/**
* {
* "rule_name": "info_provider_test",
* "rule_author": "john",
* "rule_version": 12345,
* "rule_description": "Test rule",
* "enrichments": { },
* "actions": { }
* }
**/
@Multiline
public static String testRule;
/**
* {
* "rule_name": "info_provider_test",
* "rule_author": "john",
* "rule_version": 0,
* "rule_description": "Test rule",
* "enrichments": { },
* "actions": { }
* }
**/
@Multiline
public static String testNewRule;
/**
* {
* "rules_version" : 1,
* "rules": [{
* "rule_name": "info_provider_test",
* "rule_author": "mark",
* "rule_version": 12,
* "rule_description": "Test rule",
* "enrichments": { },
* "actions": { }
* }]
* }
**/
@Multiline
public static String release;
/**
* {
* "rule_name": "../../../test",
* "rule_author": "steve",
* "rule_version": 12345,
* "rule_description": "Test rule",
* "enrichments": { },
* "actions": { }
* }
**/
@Multiline
public static String maliciousRule;
public static String user = "steve@secret.net";
private final ConfigInfoProvider infoProvider = JsonRuleConfigInfoProvider.create();
@Test
public void RuleInfoTestChangeAuthor() {
ConfigInfo info = infoProvider.getConfigInfo(user, testRule);
Assert.assertEquals(12345, info.getOldVersion());
Assert.assertEquals(12346, info.getVersion());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals("Updating rule: info_provider_test to version: 12346", info.getCommitMessage());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("info_provider_test.json"));
Assert.assertTrue(info.getFilesContent()
.get("info_provider_test.json").indexOf("\"rule_version\": 12346,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("info_provider_test.json").indexOf("\"rule_author\": \"steve\",") > 0);
Assert.assertFalse(info.isNewConfig());
}
@Test
public void RuleInfoTestUnchangedAuthor() {
ConfigInfo info = infoProvider.getConfigInfo("john@secret.net", testRule);
Assert.assertEquals(info.getOldVersion(), 12345);
Assert.assertEquals(info.getCommitter(), "john");
Assert.assertEquals(info.getCommitMessage(), "Updating rule: info_provider_test to version: 12346");
Assert.assertEquals(info.getCommitterEmail(), "john@secret.net");
Assert.assertEquals(info.getFilesContent().size(), 1);
Assert.assertEquals(info.getFilesContent().containsKey("info_provider_test.json"), true);
Assert.assertEquals(info.getFilesContent()
.get("info_provider_test.json").indexOf("\"rule_version\": 12346,") > 0, true);
Assert.assertEquals(info.getFilesContent()
.get("info_provider_test.json").indexOf("\"rule_author\": \"john\",") > 0, true);
Assert.assertEquals(info.isNewConfig(), false);
}
@Test
public void RuleInfoNewRule() {
ConfigInfo info = infoProvider.getConfigInfo(user, testNewRule);
Assert.assertEquals(info.getOldVersion(), 0);
Assert.assertEquals(info.getCommitter(), "steve");
Assert.assertEquals(info.getCommitMessage(), "Adding new rule: info_provider_test");
Assert.assertEquals(info.getCommitterEmail(), user);
Assert.assertEquals(info.getFilesContent().size(), 1);
Assert.assertEquals(info.getFilesContent().containsKey("info_provider_test.json"), true);
Assert.assertEquals(info.getFilesContent()
.get("info_provider_test.json").indexOf("\"rule_version\": 1,") > 0, true);
Assert.assertEquals(info.isNewConfig(), true);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void RuleInfoWrongJson() {
ConfigInfo info = infoProvider.getConfigInfo(user,"WRONG JSON");
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void RuleInfoWrongMissingMetadata() {
ConfigInfo info = infoProvider.getConfigInfo(user, maliciousRule);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void RuleInfoWrongUser() {
ConfigInfo info = infoProvider.getConfigInfo("INVALID", testRule);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ReleaseInfoWrongUser() {
ConfigInfo info = infoProvider.getReleaseInfo("INVALID", testRule);
}
@Test
public void ReleaseTest() {
ConfigInfo info = infoProvider.getReleaseInfo("steve@secret.net", release);
Assert.assertEquals(info.getOldVersion(), 1);
Assert.assertEquals(info.getVersion(), 2);
Assert.assertEquals(info.getCommitter(), "steve");
Assert.assertEquals(info.getCommitMessage(), "Rules released to version: 2");
Assert.assertEquals(info.getCommitter(), "steve");
Assert.assertEquals(info.getCommitterEmail(), user);
Assert.assertEquals(info.getFilesContent().size(), 1);
Assert.assertEquals(info.getFilesContent().containsKey("rules.json"), true);
Assert.assertEquals(info.getFilesContent()
.get("rules.json").indexOf("\"rules_version\": 2,") > 0, true);
}
@Test
public void FilterRulesTest() {
Assert.assertEquals(infoProvider.isReleaseFile("a.json"), false);
Assert.assertEquals(infoProvider.isReleaseFile("rules.json"), true);
Assert.assertEquals(infoProvider.isStoreFile("abc.json"), true);
Assert.assertEquals(infoProvider.isStoreFile("json.txt"), false);
}
@Test
public void RulesVersionTest() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("rules.json", release, ConfigEditorFile.ContentType.RAW_JSON_STRING));
int version = infoProvider.getReleaseVersion(files);
Assert.assertEquals(version, 1);
}
@Test(expected = IllegalArgumentException.class)
public void RulesVersionTestMissingFile() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("a.json", release, ConfigEditorFile.ContentType.RAW_JSON_STRING));
int version = infoProvider.getReleaseVersion(files);
}
@Test(expected = IllegalArgumentException.class)
public void RulesVersionMissingVersion() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("rules.json", "{}", ConfigEditorFile.ContentType.RAW_JSON_STRING));
int version = infoProvider.getReleaseVersion(files);
}
}

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>config-editor-services</artifactId>
<name>config-editor-services</name>
<packaging>jar</packaging>
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>config-editor</artifactId>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-common</artifactId>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>config-editor-core</artifactId>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nikita-core</artifactId>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-parsing-app</artifactId>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-Xlint:unchecked</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,174 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.common.ConfigSchemaService;
import uk.co.gresearch.nortem.configeditor.service.centrifuge.model.RulesWrapperDto;
import uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto;
import uk.co.gresearch.nortem.configeditor.model.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import static uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult.StatusCode.OK;
public class CentrifugeRuleSchemaImpl implements ConfigSchemaService {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final String EMPTY_UI_CONFIG = "Empty UI config provided";
private final CentrifugeSchemaService centrifugeSchemaService;
private final AtomicReference<Exception> exception = new AtomicReference<>();
CentrifugeRuleSchemaImpl(CentrifugeSchemaService centrifugeSchemaService) {
this.centrifugeSchemaService = centrifugeSchemaService;
}
public static ConfigSchemaService createCentrifugeRuleSchemaImpl(String centrifugeUrl,
Optional<String> uiConfig) throws IOException {
HttpProvider httpProvider = new HttpProvider(centrifugeUrl, HttpProvider::getKerberosHttpClient);
if (!uiConfig.isPresent()) {
throw new IllegalArgumentException(EMPTY_UI_CONFIG);
}
CentrifugeSchemaService centrifugeSchemaService = new CentrifugeSchemaService
.Builder(httpProvider, uiConfig.get())
.build();
return new CentrifugeRuleSchemaImpl(centrifugeSchemaService);
}
@Override
public ConfigEditorResult getSchema() {
String schema = this.centrifugeSchemaService.getRulesSchema();
ConfigEditorAttributes result = new ConfigEditorAttributes();
result.setRulesSchema(schema);
return new ConfigEditorResult(OK, result);
}
@Override
public ConfigEditorResult getFields() {
ConfigEditorAttributes result = new ConfigEditorAttributes();
result.setFields(centrifugeSchemaService.getFields());
return new ConfigEditorResult(OK, result);
}
private String wrapSingleRuleAsRules(String rule) throws IOException{
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(LinkedHashMap.class, String.class, Object.class);
Map<String, Object> ruleObj = mapper.readValue(rule, mapType);
RulesWrapperDto wrapper = new RulesWrapperDto();
wrapper.addToRules(ruleObj);
return mapper.writeValueAsString(wrapper);
}
@Override
public ConfigEditorResult validateConfiguration(String rule) {
try{
String ruleAsRules = wrapSingleRuleAsRules(rule);
return validateConfigurations(ruleAsRules);
} catch (IOException e){
LOG.error(e.getMessage());
ConfigEditorAttributes result = new ConfigEditorAttributes();
result.setException(e.toString());
result.setMessage(ExceptionUtils.getMessage(e));
return new ConfigEditorResult(ConfigEditorResult.StatusCode.BAD_REQUEST, result);
}
}
@Override
public ConfigEditorResult validateConfigurations(String rules) {
try {
ConfigEditorAttributes result = new ConfigEditorAttributes();
CentrifugeResponseDto response = this.centrifugeSchemaService.validateRules(rules);
ConfigEditorResult.StatusCode statusCode;
switch(response.getStatusCode()) {
case OK:
statusCode = OK;
exception.set(null);
break;
case BAD_REQUEST:
statusCode = ConfigEditorResult.StatusCode.BAD_REQUEST;
break;
case ERROR:
statusCode = ConfigEditorResult.StatusCode.ERROR;
break;
case UNAUTHORISED:
statusCode = ConfigEditorResult.StatusCode.UNAUTHORISED;
break;
default:
statusCode = ConfigEditorResult.StatusCode.ERROR;
}
result.setException(response.getAttributes().getException());
result.setMessage(response.getAttributes().getMessage());
return new ConfigEditorResult(statusCode, result);
} catch (IOException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
exception.set(e);
return ConfigEditorResult.fromException(e);
}
}
@Override
public ConfigEditorResult testConfiguration(String rule, String event) {
try{
String ruleAsRules = wrapSingleRuleAsRules(rule);
return testConfigurations(ruleAsRules, event);
} catch (IOException e){
LOG.error(e.getMessage());
ConfigEditorAttributes result = new ConfigEditorAttributes();
result.setException(e.toString());
result.setMessage(ExceptionUtils.getMessage(e));
return new ConfigEditorResult(ConfigEditorResult.StatusCode.BAD_REQUEST, result);
}
}
@Override
public ConfigEditorResult testConfigurations(String rules, String event) {
try {
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
CentrifugeResponseDto response = this.centrifugeSchemaService.testRules(rules, event);
ConfigEditorResult.StatusCode statusCode = ConfigEditorResult.StatusCode.ERROR;
switch(response.getStatusCode()) {
case OK:
exception.set(null);
attributes.setTestResultOutput(response.getAttributes().getMessage());
attributes.setTestResultComplete(true);
return new ConfigEditorResult(OK, attributes);
case BAD_REQUEST:
statusCode = ConfigEditorResult.StatusCode.BAD_REQUEST;
break;
}
attributes.setException(response.getAttributes().getException());
attributes.setMessage(response.getAttributes().getMessage());
return new ConfigEditorResult(statusCode, attributes);
} catch (IOException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
exception.set(e);
return ConfigEditorResult.fromException(e);
}
}
@Override
public Health checkHealth() {
Exception current = exception.get();
return current == null
? Health.up().build()
: Health.down().withException(current).build();
}
}

View File

@@ -0,0 +1,126 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.util.*;
class CentrifugeSchemaService {
private static final String GET_SCHEMA_PATH = "/api/v1/rules/schema";
private static final String VALIDATE_RULES_PATH = "/api/v1/rules/validate";
private static final String TEST_RULES_PATH = "/api/v1/rules/test";
private static final String FIELDS_PATH = "/api/v1/fields";
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final ObjectMapper MAPPER = new ObjectMapper();
private final HttpProvider httpProvider;
private final String rulesSchema;
private final String centrifugeFields;
CentrifugeSchemaService(Builder builder) {
this.httpProvider = builder.httpProvider;
this.rulesSchema = builder.rulesSchema;
this.centrifugeFields = builder.centrifugeFields;
}
public String getRulesSchema() {
return rulesSchema;
}
public String getFields() {
return centrifugeFields;
}
public CentrifugeResponseDto validateRules(String rules) throws IOException {
String json = this.httpProvider.post(VALIDATE_RULES_PATH, rules);
return MAPPER.readValue(json, CentrifugeResponseDto.class);
}
public CentrifugeResponseDto testRules(String rules, String event) throws IOException {
CentrifugeResponseDto.Attributes attr = new CentrifugeResponseDto.Attributes();
attr.setEvent(event);
attr.setJsonRules(rules);
String body = MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.writeValueAsString(attr);
String json = this.httpProvider.post(TEST_RULES_PATH, body);
return MAPPER.readValue(json, CentrifugeResponseDto.class);
}
public static class Builder {
private final HttpProvider httpProvider;
private final String uiConfig;
private String rulesSchema;
private String centrifugeFields;
public Builder(HttpProvider httpProvider, String uiConfig) {
this.httpProvider = httpProvider;
this.uiConfig = uiConfig;
}
public CentrifugeSchemaService build() throws IOException {
LOG.info("Obtaining and computing centrifuge schema");
Optional<String> schema = getAndComputeSchema();
if (!schema.isPresent()) {
throw new IllegalStateException("Error during obtaining centrifuge schema");
}
rulesSchema = schema.get();
LOG.info("Computation of Centrifuge schema completed");
LOG.info("Obtaining centrifuge fields");
Optional<String> fields = getFields();
if (!fields.isPresent()) {
throw new IllegalStateException("Error during obtaining centrifuge fields");
}
centrifugeFields = fields.get();
LOG.info("Obtaining centrifuge fields completed");
return new CentrifugeSchemaService(this);
}
private Optional<String> getSchema() throws IOException {
String responseBody = this.httpProvider.get(GET_SCHEMA_PATH);
CentrifugeResponseDto response = MAPPER.readValue(responseBody, CentrifugeResponseDto.class);
if (response == null
|| response.getStatusCode() != CentrifugeResponseDto.StatusCode.OK
|| response.getAttributes() == null) {
throw new IllegalStateException("Empty schema response");
}
return Optional.ofNullable(MAPPER.writeValueAsString(response.getAttributes().getSchema()));
}
private Optional<String> computeRulesSchema(String rulesSchema, String uiConfig) throws IOException {
return ConfigEditorUtils.computeRulesSchema(rulesSchema, uiConfig);
}
private Optional<String> getAndComputeSchema() throws IOException {
Optional<String> schema = getSchema();
if (!schema.isPresent()) {
throw new IllegalStateException("unable to retrieve valid schema");
}
return computeRulesSchema(schema.get(), uiConfig);
}
private Optional<String> getFields() throws IOException {
String responseBody = httpProvider.get(FIELDS_PATH);
CentrifugeResponseDto response = MAPPER.readValue(responseBody, CentrifugeResponseDto.class);
if (response == null
|| response.getStatusCode() != CentrifugeResponseDto.StatusCode.OK
|| response.getAttributes() == null) {
throw new IllegalStateException("Empty fields response");
}
return Optional.ofNullable(MAPPER.writeValueAsString(response.getAttributes().getFields()));
}
}
}

View File

@@ -0,0 +1,86 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRawValue;
import java.util.LinkedHashMap;
import java.util.List;
public class CentrifugeResponseDto {
public static class Attributes {
private String exception;
private String message;
@JsonProperty("json_rules")
@JsonRawValue
private String jsonRules;
@JsonRawValue
private String event;
@JsonProperty("rules_schema")
private LinkedHashMap<String, Object> schema;
private List<LinkedHashMap<String, Object>> fields;
public String getException(){ return exception; }
public String getMessage(){ return message; }
public void setException(String exception){
this.exception = exception;
}
public void setMessage(String message){
this.message = message;
}
public LinkedHashMap<String, Object> getSchema() {
return schema;
}
public void setSchema(LinkedHashMap<String, Object> schema) {
this.schema = schema;
}
public List<LinkedHashMap<String, Object>> getFields() {
return fields;
}
public void setFields(List<LinkedHashMap<String, Object>> fields) {
this.fields = fields;
}
public String getJsonRules() {
return jsonRules;
}
public void setJsonRules(String jsonRules) {
this.jsonRules = jsonRules;
}
public String getEvent() {
return event;
}
public void setEvent(String event) {
this.event = event;
}
}
public enum StatusCode {
OK,
ERROR,
BAD_REQUEST,
UNAUTHORISED,
}
private StatusCode statusCode;
private Attributes attributes = new Attributes();
public StatusCode getStatusCode() {return this.statusCode;}
public Attributes getAttributes() {return this.attributes;}
public void setStatusCode(StatusCode statusCode){
this.statusCode = statusCode;
}
public void setAttributes(Attributes attributes){
this.attributes = attributes;
}
}

View File

@@ -0,0 +1,26 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
public class RulesWrapperDto {
@JsonProperty("rules_version")
private int rulesVersion = 1;
@JsonProperty("rules")
private List<Object> rules = new ArrayList<>();
public void setRulesVersion(int rulesVersion) {
this.rulesVersion = rulesVersion;
}
public void setRules(List<Object> rules){
this.rules = rules;
}
public int getRulesVersion() {return this.rulesVersion;}
public List<Object> getRules() {return this.rules;}
public void addToRules(Object rule){
this.rules.add(rule);
}
}

View File

@@ -0,0 +1,79 @@
package uk.co.gresearch.nortem.configeditor.service.elk;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.model.TemplateField;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
public class ElkProvider {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String EMPTY_MSG = "Empty template fields";
private static final String MAPPINGS_FIELD = "mappings";
private static final String PROPERTIES_FIELD = "properties";
private static final String TEMPLATE_SUFFIX = "_doc";
private final HttpProvider httpClient;
private final String templatePath;
public ElkProvider(HttpProvider httpClient, String templatePath) {
this.httpClient = httpClient;
this.templatePath = templatePath;
}
private Optional<String> getSensorName(Iterator<String> it) {
for (; it.hasNext(); ) {
String current = it.next();
if (current.endsWith(TEMPLATE_SUFFIX)
&& current.length() > TEMPLATE_SUFFIX.length()) {
return Optional.of(
current.substring(0, current.length() - TEMPLATE_SUFFIX.length()));
}
}
return Optional.empty();
}
private List<TemplateField> getFields(JsonNode properties) {
List<TemplateField> ret = new ArrayList<>();
properties.fieldNames().forEachRemaining(x -> ret.add(new TemplateField(x)));
return ret;
}
public Map<String, List<TemplateField>> getTemplateFields() throws IOException {
Map<String, List<TemplateField>> ret = new HashMap<>();
String response = httpClient.get(templatePath);
LOG.info(String.format("Template to init:\n%s", response));
JsonNode template = MAPPER.readTree(response);
for (Iterator<JsonNode> it = template.iterator(); it.hasNext(); ) {
JsonNode mappings = it.next().get(MAPPINGS_FIELD);
if (mappings == null) {
continue;
}
Optional<String> sensorName = getSensorName(mappings.fieldNames());
if (!sensorName.isPresent()) {
continue;
}
List<TemplateField> fields = getFields(mappings.findValue(PROPERTIES_FIELD));
if (fields.isEmpty()) {
continue;
}
ret.put(sensorName.get(), fields);
}
if (ret.isEmpty()) {
throw new IllegalStateException(EMPTY_MSG);
}
LOG.info(String.format("Computed fields:\n%s", ret.toString()));
return ret;
}
}

View File

@@ -0,0 +1,18 @@
package uk.co.gresearch.nortem.configeditor.service.elk;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
public interface ElkService {
ConfigEditorResult getTemplateFields(String sensor);
ConfigEditorResult getTemplateFields();
ConfigEditorResult shutDown();
ConfigEditorResult awaitShutDown();
Health checkHealth();
}

View File

@@ -0,0 +1,123 @@
package uk.co.gresearch.nortem.configeditor.service.elk;
import java.io.IOException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.SensorTemplateFields;
import uk.co.gresearch.nortem.configeditor.model.TemplateField;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class ElkServiceImpl implements ElkService {
private static final String NO_FIELDS_FOR_SENSOR = "No fields available for the sensor";
private static final int TEMPLATE_UPDATE_PERIOD_IN_SEC = 4 * 3600;
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private final ElkProvider elkProvider;
private final AtomicReference<Map<String, List<TemplateField>>> cache = new AtomicReference<>();
private final AtomicReference<Exception> exception = new AtomicReference<>();
private final ScheduledThreadPoolExecutor executor;
public ElkServiceImpl(ElkProvider elkProvider,
Map<String, List<TemplateField>> template,
int updatePeriodInSec) {
this.elkProvider = elkProvider;
this.cache.set(template);
executor = new ScheduledThreadPoolExecutor(1);
executor.setRemoveOnCancelPolicy(true);
executor.scheduleAtFixedRate(() -> updateCache(),
updatePeriodInSec,
updatePeriodInSec,
TimeUnit.SECONDS);
}
@Override
public ConfigEditorResult getTemplateFields(String sensor) {
Map<String, List<TemplateField>> current = cache.get();
if (!current.containsKey(sensor)) {
return ConfigEditorResult.fromMessage(
ConfigEditorResult.StatusCode.BAD_REQUEST,
NO_FIELDS_FOR_SENSOR);
}
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setTemplateFields(current.get(sensor));
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attributes);
}
@Override
public ConfigEditorResult getTemplateFields() {
Map<String, List<TemplateField>> current = cache.get();
List<SensorTemplateFields> sensorTemplateFields = current
.keySet()
.stream()
.map(x -> new SensorTemplateFields(x, current.get(x)))
.collect(Collectors.toList());
ConfigEditorAttributes attributes = new ConfigEditorAttributes();
attributes.setSensorTemplateFields(sensorTemplateFields);
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attributes);
}
private void updateCache() {
try {
LOG.info("Initiating updating elk fields cache");
Map<String, List<TemplateField>> update = elkProvider.getTemplateFields();
cache.set(update);
exception.set(null);
LOG.info("Updating elk fields cache completed");
} catch (Exception e) {
LOG.error("Problem during obtaining template fields from elk with exception {}",
ExceptionUtils.getStackTrace(e));
exception.set(e);
}
}
@Override
public ConfigEditorResult shutDown() {
executor.shutdown();
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK);
}
@Override
public ConfigEditorResult awaitShutDown() {
executor.shutdownNow();
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK);
}
@Override
public Health checkHealth() {
Exception current = exception.get();
return current == null
? Health.up().build()
: Health.down().withException(current).build();
}
public static ElkService createElkServiceImpl(String elkUrl, String templatePath) throws IOException {
LOG.info("Initialising Elk Service");
HttpProvider httpProvider = new HttpProvider(elkUrl, HttpProvider::getKerberosHttpClient);
ElkProvider elkProvider = new ElkProvider(httpProvider, templatePath);
Map<String, List<TemplateField>> fields = elkProvider.getTemplateFields();
ElkService ret = new ElkServiceImpl(elkProvider, fields, TEMPLATE_UPDATE_PERIOD_IN_SEC);
LOG.info("Initialising Elk Service completed");
return ret;
}
}

View File

@@ -0,0 +1,136 @@
package uk.co.gresearch.nortem.configeditor.service.nikita;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import uk.co.gresearch.nortem.configeditor.common.ConfigSchemaService;
import uk.co.gresearch.nortem.nikita.common.NikitaResult;
import uk.co.gresearch.nortem.nikita.compiler.NikitaCompiler;
import uk.co.gresearch.nortem.nikita.compiler.NikitaCorrelationRulesCompiler;
import uk.co.gresearch.nortem.nikita.compiler.NikitaRulesCompiler;
import java.lang.invoke.MethodHandles;
import java.util.Optional;
public class NikitaRuleSchemaServiceImpl implements ConfigSchemaService {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final String SCHEMA_INIT_ERROR = "Error during computing rules schema";
private static final String TESTING_ERROR = "Unexpected rule testing service result";
private final NikitaCompiler nikitaCompiler;
private final String schema;
NikitaRuleSchemaServiceImpl(NikitaCompiler nikitaCompiler, String schema) {
this.nikitaCompiler = nikitaCompiler;
this.schema = schema;
}
@Override
public ConfigEditorResult getSchema() {
return ConfigEditorResult.fromSchema(schema);
}
@Override
public ConfigEditorResult validateConfiguration(String rule) {
NikitaResult nikitaResult = nikitaCompiler.validateRule(rule);
return fromNikitaValidateResult(nikitaResult);
}
@Override
public ConfigEditorResult validateConfigurations(String rules) {
NikitaResult nikitaResult = nikitaCompiler.validateRules(rules);
return fromNikitaValidateResult(nikitaResult);
}
public static ConfigSchemaService createNikitaRuleSchema(Optional<String> uiConfig) throws Exception {
LOG.info("Initialising nikita rule schema service");
NikitaCompiler compiler = NikitaRulesCompiler.createNikitaRulesCompiler();
NikitaResult schemaResult = compiler.getSchema();
if (schemaResult.getStatusCode() != NikitaResult.StatusCode.OK
|| schemaResult.getAttributes().getRulesSchema() == null
|| !uiConfig.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
Optional<String> computedSchema = ConfigEditorUtils
.computeRulesSchema(schemaResult.getAttributes().getRulesSchema(), uiConfig.get());
if (!computedSchema.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
LOG.info("Initialising nikita rule schema service completed");
return new NikitaRuleSchemaServiceImpl(compiler, computedSchema.get());
}
public static ConfigSchemaService createNikitaCorrelationRuleSchema(
Optional<String> uiConfig) throws Exception {
LOG.info("Initialising nikita correlation rule schema service");
NikitaCompiler compiler = NikitaCorrelationRulesCompiler.createNikitaCorrelationRulesCompiler();
NikitaResult schemaResult = compiler.getSchema();
if (schemaResult.getStatusCode() != NikitaResult.StatusCode.OK
|| schemaResult.getAttributes().getRulesSchema() == null
|| !uiConfig.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
Optional<String> computedSchema = ConfigEditorUtils
.computeRulesSchema(schemaResult.getAttributes().getRulesSchema(), uiConfig.get());
if (!computedSchema.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
LOG.info("Initialising nikita correlation rule schema service completed");
return new NikitaRuleSchemaServiceImpl(compiler, computedSchema.get());
}
@Override
public ConfigEditorResult testConfiguration(String rule, String event) {
return fromNikitaTestResult(nikitaCompiler.testRule(rule, event));
}
@Override
public ConfigEditorResult testConfigurations(String rule, String event) {
return fromNikitaTestResult(nikitaCompiler.testRules(rule, event));
}
private ConfigEditorResult fromNikitaValidateResult(NikitaResult nikitaResult) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
ConfigEditorResult.StatusCode statusCode = nikitaResult.getStatusCode() == NikitaResult.StatusCode.OK
? ConfigEditorResult.StatusCode.OK
: ConfigEditorResult.StatusCode.ERROR;
attr.setMessage(nikitaResult.getAttributes().getMessage());
attr.setException(nikitaResult.getAttributes().getException());
return new ConfigEditorResult(statusCode, attr);
}
private ConfigEditorResult fromNikitaTestResult(NikitaResult nikitaResult) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
if (nikitaResult.getStatusCode() != NikitaResult.StatusCode.OK) {
attr.setMessage(nikitaResult.getAttributes().getMessage());
attr.setException(nikitaResult.getAttributes().getException());
return new ConfigEditorResult(ConfigEditorResult.StatusCode.ERROR, attr);
}
if (nikitaResult.getAttributes().getMessage() == null) {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR,
TESTING_ERROR);
}
attr.setTestResultComplete(true);
attr.setTestResultOutput(nikitaResult.getAttributes().getMessage());
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
}
}

View File

@@ -0,0 +1,23 @@
package uk.co.gresearch.nortem.configeditor.service.parserconfig;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfoProvider;
import uk.co.gresearch.nortem.configeditor.configstore.JsonConfigInfoProvider;
public class ParserConfigConfigInfoProvider {
private static final String AUTHOR_FIELD = "parser_author";
private static final String NAME_FIELD = "parser_name";
private static final String VERSION_FIELD = "parser_version";
private static final String RELEASE_VERSION_FIELD = "parsers_version";
private static final String PARSERS_FILENAME = "parsers.json";
public static ConfigInfoProvider create() {
return new JsonConfigInfoProvider.Builder()
.configAuthorField(AUTHOR_FIELD)
.configNameField(NAME_FIELD)
.configsVersionField(RELEASE_VERSION_FIELD)
.configVersionField(VERSION_FIELD)
.useConfigWordingInGitMessages()
.releaseFilename(PARSERS_FILENAME)
.build();
}
}

View File

@@ -0,0 +1,134 @@
package uk.co.gresearch.nortem.configeditor.service.parserconfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import uk.co.gresearch.nortem.configeditor.common.ConfigSchemaService;
import uk.co.gresearch.nortem.parsers.common.ParserResult;
import uk.co.gresearch.nortem.parsers.factory.ParserFactory;
import uk.co.gresearch.nortem.parsers.factory.ParserFactoryImpl;
import uk.co.gresearch.nortem.parsers.factory.ParserFactoryResult;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ParserConfigSchemaServiceImpl implements ConfigSchemaService {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final ObjectWriter JSON_WRITER = new ObjectMapper()
.writerFor(new TypeReference<List<Map<String, Object>>>() { })
.with(SerializationFeature.INDENT_OUTPUT);
private static final ObjectReader TEST_LOG_READER = new ObjectMapper()
.readerFor(TestSpecificationDto.class);
private final ParserFactory parserFactory;
private final String schema;
ParserConfigSchemaServiceImpl(ParserFactory parserFactory, String schema) {
this.parserFactory = parserFactory;
this.schema = schema;
}
@Override
public ConfigEditorResult getSchema() {
return ConfigEditorResult.fromSchema(schema);
}
@Override
public ConfigEditorResult validateConfiguration(String config) {
ParserFactoryResult parserResult = parserFactory.validateConfiguration(config);
return fromParserFactoryValidateResult(parserResult);
}
@Override
public ConfigEditorResult validateConfigurations(String configs) {
ParserFactoryResult parserResult = parserFactory.validateConfigurations(configs);
return fromParserFactoryValidateResult(parserResult);
}
public static ConfigSchemaService createParserConfigSchemaServiceImpl(Optional<String> uiConfig) throws Exception {
LOG.info("Initialising parser config schema service");
ParserFactory parserFactory = ParserFactoryImpl.createParserFactory();
ParserFactoryResult schemaResult = parserFactory.getSchema();
if (schemaResult.getStatusCode() != ParserFactoryResult.StatusCode.OK
|| schemaResult.getAttributes().getJsonSchema() == null
|| !uiConfig.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
Optional<String> computedSchema = ConfigEditorUtils
.computeRulesSchema(schemaResult.getAttributes().getJsonSchema(), uiConfig.get());
if (!computedSchema.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
LOG.info("Initialising parser config schema service completed");
return new ParserConfigSchemaServiceImpl(parserFactory, computedSchema.get());
}
@Override
public ConfigEditorResult testConfiguration(String config, String event) {
TestSpecificationDto test;
try {
test = TEST_LOG_READER.readValue(event);
} catch (IOException e) {
return ConfigEditorResult.fromException(e);
}
ParserFactoryResult parserResult = parserFactory.test(config, test.getLog().getBytes());
return fromParserFactoryTestResult(parserResult);
}
private ConfigEditorResult fromParserFactoryValidateResult(ParserFactoryResult parserResult) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
ConfigEditorResult.StatusCode statusCode = parserResult.getStatusCode() == ParserFactoryResult.StatusCode.OK
? ConfigEditorResult.StatusCode.OK
: ConfigEditorResult.StatusCode.ERROR;
attr.setMessage(parserResult.getAttributes().getMessage());
return new ConfigEditorResult(statusCode, attr);
}
private ConfigEditorResult fromParserFactoryTestResult(ParserFactoryResult parserFactoryResult) {
ConfigEditorAttributes attr = new ConfigEditorAttributes();
if (parserFactoryResult.getStatusCode() != ParserFactoryResult.StatusCode.OK
|| parserFactoryResult.getAttributes().getParserResult() == null) {
attr.setMessage(parserFactoryResult.getAttributes().getMessage());
return new ConfigEditorResult(ConfigEditorResult.StatusCode.ERROR, attr);
}
ParserResult result = parserFactoryResult.getAttributes().getParserResult();
if (result.getException() != null) {
return ConfigEditorResult.fromMessage(ConfigEditorResult.StatusCode.ERROR,
ExceptionUtils.getStackTrace(result.getException()));
}
attr.setTestResultComplete(true);
try {
String output = JSON_WRITER.writeValueAsString(result.getParsedMessages());
attr.setTestResultOutput(output);
} catch (JsonProcessingException e) {
return ConfigEditorResult.fromException(e);
}
return new ConfigEditorResult(ConfigEditorResult.StatusCode.OK, attr);
}
}

View File

@@ -0,0 +1,13 @@
package uk.co.gresearch.nortem.configeditor.service.parserconfig;
public class TestSpecificationDto {
private String log;
public String getLog() {
return log;
}
public void setLog(String log) {
this.log = log;
}
}

View File

@@ -0,0 +1,23 @@
package uk.co.gresearch.nortem.configeditor.service.parsingapp;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfoProvider;
import uk.co.gresearch.nortem.configeditor.configstore.JsonConfigInfoProvider;
public class ParsingAppConfigInfoProvider {
private static final String AUTHOR_FIELD = "parsing_app_author";
private static final String NAME_FIELD = "parsing_app_name";
private static final String VERSION_FIELD = "parsing_app_version";
private static final String RELEASE_VERSION_FIELD = "parsing_applications_version";
private static final String PARSERS_FILENAME = "parsing_applications.json";
public static ConfigInfoProvider create() {
return new JsonConfigInfoProvider.Builder()
.configAuthorField(AUTHOR_FIELD)
.configNameField(NAME_FIELD)
.configsVersionField(RELEASE_VERSION_FIELD)
.configVersionField(VERSION_FIELD)
.useConfigWordingInGitMessages()
.releaseFilename(PARSERS_FILENAME)
.build();
}
}

View File

@@ -0,0 +1,87 @@
package uk.co.gresearch.nortem.configeditor.service.parsingapp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorAttributes;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import uk.co.gresearch.nortem.configeditor.common.ConfigSchemaService;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactory;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryImpl;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryResult;
import java.lang.invoke.MethodHandles;
import java.util.Optional;
import java.util.function.Function;
import static uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult.StatusCode.ERROR;
import static uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult.StatusCode.OK;
public class ParsingAppConfigSchemaServiceImpl implements ConfigSchemaService {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private final ParsingApplicationFactory factory;
private final String schema;
ParsingAppConfigSchemaServiceImpl(ParsingApplicationFactory factory, String schema) {
this.factory = factory;
this.schema = schema;
}
@Override
public ConfigEditorResult getSchema() {
return ConfigEditorResult.fromSchema(schema);
}
@Override
public ConfigEditorResult validateConfiguration(String configs) {
return validate(configs, x -> factory.validateConfiguration(x));
}
@Override
public ConfigEditorResult validateConfigurations(String configs) {
return validate(configs, x -> factory.validateConfigurations(x));
}
private ConfigEditorResult validate(String config, Function<String, ParsingApplicationFactoryResult> fun) {
ParsingApplicationFactoryResult factoryResult = fun.apply(config);
ConfigEditorAttributes attr = new ConfigEditorAttributes();
if (factoryResult.getStatusCode() == ParsingApplicationFactoryResult.StatusCode.ERROR) {
attr.setMessage(factoryResult.getAttributes().getMessage());
}
ConfigEditorResult.StatusCode statusCode =
factoryResult.getStatusCode() == ParsingApplicationFactoryResult.StatusCode.OK
? OK
: ERROR;
return new ConfigEditorResult(statusCode, attr);
}
public static ParsingAppConfigSchemaServiceImpl createParserConfigSchemaServiceImpl(
Optional<String> uiConfig) throws Exception {
LOG.info("Initialising parsing app config schema service");
ParsingApplicationFactory factory = new ParsingApplicationFactoryImpl();
ParsingApplicationFactoryResult schemaResult = factory.getSchema();
if (schemaResult.getStatusCode() != ParsingApplicationFactoryResult.StatusCode.OK
|| schemaResult.getAttributes().getJsonSchema() == null
|| !uiConfig.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
Optional<String> computedSchema = ConfigEditorUtils
.computeRulesSchema(schemaResult.getAttributes().getJsonSchema(), uiConfig.get());
if (!computedSchema.isPresent()) {
LOG.error(SCHEMA_INIT_ERROR);
throw new IllegalStateException(SCHEMA_INIT_ERROR);
}
LOG.info("Initialising parsing app schema service completed");
return new ParsingAppConfigSchemaServiceImpl(factory, computedSchema.get());
}
}

View File

@@ -0,0 +1,145 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge;
import org.adrianwalker.multilinestring.Multiline;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto;
import java.io.*;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
public class CentrifugeSchemaServiceTest {
private HttpProvider mockHttp;
private CentrifugeSchemaService centrifugeSchemaService;
private String rulesSchemaResponse;
private String fieldsResponse;
private String uiConfigTest;
/**
{
"statusCode": "BAD_REQUEST",
"attributes": {
"exception": "com.fasterxml.jackson.core.JsonParseException: Unexpected character ..."
}
}
*
**/
@Multiline
private static String validateBadRequest;
/**
{
"statusCode": "OK"
}
*
**/
@Multiline
private static String validateOK;
@Before
public void setup() throws IOException {
mockHttp = Mockito.mock(HttpProvider.class);
rulesSchemaResponse = ConfigEditorUtils.readTextFromResources("testSchema.json").get();
fieldsResponse = ConfigEditorUtils.readTextFromResources("fieldsResponse.json").get();
uiConfigTest = ConfigEditorUtils.readTextFromResources("uiConfigTest.json").get();
Mockito.when(mockHttp.get("/api/v1/rules/schema")).thenReturn(rulesSchemaResponse);
Mockito.when(mockHttp.get("/api/v1/fields")).thenReturn(fieldsResponse);
}
@Test
public void serviceBuildOK() throws IOException {
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
Assert.assertNotNull(centrifugeSchemaService.getFields());
Assert.assertNotNull(centrifugeSchemaService.getRulesSchema());
verify(mockHttp, times(1)).get("/api/v1/rules/schema");
verify(mockHttp, times(1)).get("/api/v1/fields");
}
@Test
public void testNonExistentFileReturnsDefault() {
String defaultLayout = ConfigEditorUtils.readUiLayoutFile("INVALID").get();
Assert.assertEquals("{\"layout\": {}}", defaultLayout);
}
@Test(expected = IOException.class)
public void serviceExceptionRuleSchema() throws IOException {
Mockito.when(mockHttp.get("/api/v1/rules/schema")).thenThrow(new IOException());
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = IOException.class)
public void serviceWrongRuleSchema() throws IOException {
Mockito.when(mockHttp.get("/api/v1/rules/schema")).thenReturn("INVALID JSON");
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = IllegalStateException.class)
public void serviceEmptyRuleSchema2() throws IOException {
Mockito.when(mockHttp.get("/api/v1/rules/schema")).thenReturn("{}");
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = IOException.class)
public void serviceExceptionFields() throws IOException {
Mockito.when(mockHttp.get("/api/v1/fields")).thenThrow(new IOException());
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = IOException.class)
public void serviceWrongFields() throws IOException {
Mockito.when(mockHttp.get("/api/v1/fields")).thenReturn("INVALID JSON");
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = IllegalStateException.class)
public void serviceEmptyFields() throws IOException {
Mockito.when(mockHttp.get("/api/v1/fields")).thenReturn("{}");
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
}
@Test(expected = com.fasterxml.jackson.core.JsonParseException.class)
public void wrongUIConfig() throws IOException {
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, "INVALID")
.build();
}
@Test
public void validateRuleOK() throws IOException {
Mockito.when(mockHttp.post("/api/v1/rules/validate", "DUMMY")).thenReturn(validateOK);
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
CentrifugeResponseDto response = centrifugeSchemaService.validateRules("DUMMY");
Assert.assertEquals(CentrifugeResponseDto.StatusCode.OK, response.getStatusCode());
}
@Test
public void validateRuleBad() throws IOException {
Mockito.when(mockHttp.post("/api/v1/rules/validate", "DUMMY")).thenReturn(validateBadRequest);
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
CentrifugeResponseDto response = centrifugeSchemaService.validateRules("DUMMY");
Assert.assertEquals(CentrifugeResponseDto.StatusCode.BAD_REQUEST, response.getStatusCode());
}
@Test(expected = IOException.class)
public void validateRuleException() throws IOException {
Mockito.when(mockHttp.post("/api/v1/rules/validate", "DUMMY")).thenThrow(new IOException());
centrifugeSchemaService = new CentrifugeSchemaService.Builder(mockHttp, uiConfigTest)
.build();
centrifugeSchemaService.validateRules("DUMMY");
}
}

View File

@@ -0,0 +1,189 @@
package uk.co.gresearch.nortem.configeditor.service.centrifuge;
import org.adrianwalker.multilinestring.Multiline;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.boot.actuate.health.Status;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto.StatusCode.ERROR;
import static uk.co.gresearch.nortem.configeditor.service.centrifuge.model.CentrifugeResponseDto.StatusCode.OK;
public class RuleCentrifugeSchemaServiceImplTest {
private CentrifugeSchemaService service;
/**
*{"rule_name": "Walle_heartbeat","rule_author": "mariann","rule_version": 1,"rule_description": "A Rule for implementing health check.","conditions": [{"field_name": "ava:heartbeat","field_pattern": "true"}],"enrichments": {},"actions": {"elk_store": false,"heartbeat": true}}
*/
@Multiline
private static String ruleJson;
/**
*{"rules_version":1,"rules":[{"rule_name":"Walle_heartbeat","rule_author":"mariann","rule_version":1,"rule_description":"A Rule for implementing health check.","conditions":[{"field_name":"ava:heartbeat","field_pattern":"true"}],"enrichments":{},"actions":{"elk_store":false,"heartbeat":true}}]}*/
@Multiline
private static String expectedWrappedRuleJson;
private CentrifugeRuleSchemaImpl schemaServiceImpl;
private CentrifugeResponseDto response;
@Before
public void Setup() {
service = Mockito.mock(CentrifugeSchemaService.class);
this.schemaServiceImpl = new CentrifugeRuleSchemaImpl(service);
response = new CentrifugeResponseDto();
}
@Test
public void getRulesSchemaOK() {
Mockito.when(service.getRulesSchema()).thenReturn("DUMMY");
ConfigEditorResult ret = schemaServiceImpl.getSchema();
verify(service, times(1)).getRulesSchema();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals("DUMMY", ret.getAttributes().getRulesSchema());
}
@Test
public void getFieldsOK() {
Mockito.when(service.getFields()).thenReturn("DUMMY");
ConfigEditorResult ret = schemaServiceImpl.getFields();
verify(service, times(1)).getFields();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals("DUMMY", ret.getAttributes().getFields());
}
@Test
public void validateRulesOK() throws IOException {
Mockito.when(service.validateRules("DUMMY")).thenReturn(response);
response.setStatusCode(OK);
ConfigEditorResult ret = schemaServiceImpl.validateConfigurations("DUMMY");
verify(service, times(1)).validateRules("DUMMY");
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void validateRulesBad() throws IOException {
Mockito.when(service.validateRules("DUMMY")).thenReturn(response);
response.setStatusCode(ERROR);
response.getAttributes().setMessage("MESSAGE");
response.getAttributes().setException("EXCEPTION");
ConfigEditorResult ret = schemaServiceImpl.validateConfigurations("DUMMY");
verify(service, times(1)).validateRules("DUMMY");
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("MESSAGE", ret.getAttributes().getMessage() );
Assert.assertEquals("EXCEPTION", ret.getAttributes().getException());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void validateRulesException() throws IOException {
Mockito.when(service.validateRules("DUMMY")).thenThrow(new IOException());
ConfigEditorResult ret = schemaServiceImpl.validateConfigurations("DUMMY");
verify(service, times(1)).validateRules("DUMMY");
Assert.assertEquals( ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getException().contains("java.io.IOException"));
Assert.assertEquals(Status.DOWN, schemaServiceImpl.checkHealth().getStatus());
response.setStatusCode(OK);
Mockito.when(service.validateRules("DUMMY2")).thenReturn(response);
ret = schemaServiceImpl.validateConfigurations("DUMMY2");
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void validateRule() throws IOException {
response.setStatusCode(OK);
Mockito.when(service.validateRules(anyString())).thenReturn(response);
ConfigEditorResult ret = schemaServiceImpl.validateConfiguration(ruleJson);
ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
verify(service).validateRules(argument.capture());
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
Assert.assertEquals(expectedWrappedRuleJson, argument.getValue());
}
@Test
public void testRulesOK() throws IOException {
Mockito.when(service.testRules("DUMMY_RULES", "DUMMY_EVENT")).thenReturn(response);
response.setStatusCode(OK);
response.getAttributes().setMessage("DUMMY_RESULT");
ConfigEditorResult ret = schemaServiceImpl.testConfigurations("DUMMY_RULES", "DUMMY_EVENT");
verify(service, times(1)).testRules("DUMMY_RULES", "DUMMY_EVENT");
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getTestResultComplete());
Assert.assertEquals("DUMMY_RESULT", ret.getAttributes().getTestResultOutput());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void testRulesBad() throws IOException {
Mockito.when(service.testRules("DUMMY_RULES", "DUMMY_EVENT")).thenReturn(response);
response.setStatusCode(ERROR);
response.getAttributes().setMessage("MESSAGE");
response.getAttributes().setException("EXCEPTION");
ConfigEditorResult ret = schemaServiceImpl.testConfigurations("DUMMY_RULES", "DUMMY_EVENT");
verify(service, times(1)).testRules("DUMMY_RULES", "DUMMY_EVENT");
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("MESSAGE", ret.getAttributes().getMessage());
Assert.assertEquals("EXCEPTION", ret.getAttributes().getException());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void testRulesException() throws IOException {
Mockito.when(service.testRules("DUMMY_RULES", "DUMMY_EVENT"))
.thenThrow(new IOException());
ConfigEditorResult ret = schemaServiceImpl.testConfigurations("DUMMY_RULES", "DUMMY_EVENT");
verify(service, times(1)).testRules("DUMMY_RULES", "DUMMY_EVENT");
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getException().contains("java.io.IOException"));
Assert.assertEquals(Status.DOWN, schemaServiceImpl.checkHealth().getStatus());
response.setStatusCode(OK);
Mockito.when(service.testRules("DUMMY_RULES_2", "DUMMY_EVENT")).thenReturn(response);
ret = schemaServiceImpl.testConfigurations("DUMMY_RULES_2", "DUMMY_EVENT");
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
}
@Test
public void testRule() throws IOException {
response.setStatusCode(OK);
Mockito.when(service.testRules(anyString(), anyString())).thenReturn(response);
ConfigEditorResult ret = schemaServiceImpl.testConfiguration(ruleJson, "{}");
ArgumentCaptor<String> argument1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> argument2= ArgumentCaptor.forClass(String.class);
verify(service).testRules(argument1.capture(), argument2.capture());
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(Status.UP, schemaServiceImpl.checkHealth().getStatus());
Assert.assertEquals(expectedWrappedRuleJson, argument1.getValue());
Assert.assertEquals("{}", argument2.getValue());
}
@Test
public void testRuleBad() {
ConfigEditorResult ret = schemaServiceImpl.testConfiguration("INVALID", "{}");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getException().contains("JsonParseException"));
}
@Test
public void validateRuleBad() {
ConfigEditorResult ret = schemaServiceImpl.validateConfiguration("INVALID");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getException().contains("JsonParseException"));
}
}

View File

@@ -0,0 +1,69 @@
package uk.co.gresearch.nortem.configeditor.service.elk;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.common.utils.HttpProvider;
import uk.co.gresearch.nortem.configeditor.common.ConfigEditorUtils;
import uk.co.gresearch.nortem.configeditor.model.TemplateField;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
public class ElkProviderTest {
private HttpProvider mockHttp;
private String templateResponse;
private ElkProvider provider;
private String templatePath = "dummy_path";
@Before
public void setup() throws IOException {
mockHttp = Mockito.mock(HttpProvider.class);
templateResponse = ConfigEditorUtils.readTextFromResources("getTemplateResponse.json").get();
Mockito.when(mockHttp.get(templatePath)).thenReturn(templateResponse);
provider = new ElkProvider(mockHttp, templatePath);
}
@Test
public void getTemplates() throws IOException {
Map<String, List<TemplateField>> ret = provider.getTemplateFields();
verify(mockHttp, times(1)).get(templatePath);
Assert.assertEquals(2, ret.size());
Assert.assertEquals(6, ret.get("secretlogs").size());
Assert.assertEquals(4, ret.get("plaintext").size());
Assert.assertEquals("security:category", ret.get("secretlogs").get(0).getName());
Assert.assertEquals("security:level", ret.get("secretlogs").get(1).getName());
Assert.assertEquals("ip_src_addr", ret.get("secretlogs").get(2).getName());
Assert.assertEquals("ip_dst_addr", ret.get("secretlogs").get(3).getName());
Assert.assertEquals("ip_dst_port", ret.get("secretlogs").get(4).getName());
Assert.assertEquals("ip_src_port", ret.get("secretlogs").get(5).getName());
Assert.assertEquals("dlp:category", ret.get("plaintext").get(0).getName());
Assert.assertEquals("dlp:level", ret.get("plaintext").get(1).getName());
Assert.assertEquals("ip_src_addr", ret.get("plaintext").get(2).getName());
Assert.assertEquals("ip_dst_port", ret.get("plaintext").get(3).getName());
}
@Test(expected = IOException.class)
public void getTemplatesHttpProviderException() throws IOException {
Mockito.when(mockHttp.get(templatePath)).thenThrow(new IOException());
provider.getTemplateFields();
}
@Test(expected = IOException.class)
public void getTemplatesWrongJson() throws IOException {
Mockito.when(mockHttp.get(templatePath)).thenReturn("INVALID");
provider.getTemplateFields();
}
@Test(expected = IllegalStateException.class)
public void getTemplatesEmptyJson() throws IOException {
Mockito.when(mockHttp.get(templatePath)).thenReturn("{}");
provider.getTemplateFields();
}
}

View File

@@ -0,0 +1,121 @@
package uk.co.gresearch.nortem.configeditor.service.elk;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.configeditor.model.SensorTemplateFields;
import uk.co.gresearch.nortem.configeditor.model.TemplateField;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
public class ElkServiceImplTest {
private ElkProvider elkProvider;
private ElkServiceImpl elkService;
private final List<String> dummyFieldNames = Arrays.asList("a", "b", "c");
private final List<String> updatedFieldNames = Arrays.asList("d");
private final String sensorName = "secret";
private Map<String, List<TemplateField>> template;
private Map<String, List<TemplateField>> updateTemplate;
@Before
public void Setup() throws IOException {
elkProvider = Mockito.mock(ElkProvider.class);
template = new HashMap<>();
template.put(sensorName,
dummyFieldNames.stream().map(x -> new TemplateField(x)).collect(Collectors.toList()));
updateTemplate = new HashMap<>();
updateTemplate.put(sensorName,
updatedFieldNames.stream().map(x -> new TemplateField(x)).collect(Collectors.toList()));
Mockito.when(elkProvider.getTemplateFields()).thenReturn(updateTemplate);
}
@Test
public void getTemplateFieldsOK() throws IOException {
elkService = new ElkServiceImpl(elkProvider, template, 1000);
ConfigEditorResult ret = elkService.getTemplateFields();
Assert.assertEquals( ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
List<SensorTemplateFields> sensorFields = ret.getAttributes().getSensorTemplateFields();
Assert.assertEquals(1, sensorFields.size());
Assert.assertEquals("secret", sensorFields.get(0).getSensorName());
Assert.assertEquals(3, sensorFields.get(0).getFields().size());
Assert.assertEquals("a", sensorFields.get(0).getFields().get(0).getName());
Assert.assertEquals("b", sensorFields.get(0).getFields().get(1).getName());
Assert.assertEquals("c", sensorFields.get(0).getFields().get(2).getName());
elkService.shutDown();
}
@Test
public void getSensorTemplateFieldsNotExisted() throws IOException {
elkService = new ElkServiceImpl(elkProvider, template, 1000);
ConfigEditorResult ret = elkService.getTemplateFields("UNKNOWN");
Assert.assertEquals(ConfigEditorResult.StatusCode.BAD_REQUEST, ret.getStatusCode());
elkService.shutDown();
}
@Test
public void getSensorTemplateFieldOK() throws IOException {
elkService = new ElkServiceImpl(elkProvider, template, 1000);
ConfigEditorResult ret = elkService.getTemplateFields(sensorName);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
List<TemplateField> fields = ret.getAttributes().getTemplateFields();
Assert.assertEquals(3, fields.size());
Assert.assertEquals("a", fields.get(0).getName());
Assert.assertEquals("b", fields.get(1).getName());
Assert.assertEquals("c", fields.get(2).getName());
elkService.shutDown();
}
@Test
public void updateOK() throws IOException, InterruptedException {
elkService = new ElkServiceImpl(elkProvider, template, 1);
Thread.sleep(3000);
ConfigEditorResult ret = elkService.getTemplateFields();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
List<SensorTemplateFields> sensorFields = ret.getAttributes().getSensorTemplateFields();
Assert.assertEquals(1, sensorFields.size());
Assert.assertEquals("secret", sensorFields.get(0).getSensorName());
Assert.assertEquals(1, sensorFields.get(0).getFields().size());
Assert.assertEquals("d", sensorFields.get(0).getFields().get(0).getName());
ret = elkService.getTemplateFields(sensorName);
List<TemplateField> fields = ret.getAttributes().getTemplateFields();
Assert.assertEquals(1, fields.size());
Assert.assertEquals("d", fields.get(0).getName());
elkService.shutDown();
}
@Test
public void healthCheck() throws IOException, InterruptedException, ExecutionException {
elkService = new ElkServiceImpl(elkProvider, template, 1);
Health health = elkService.checkHealth();
Assert.assertEquals( Status.UP, health.getStatus());
Mockito.when(elkProvider.getTemplateFields())
.thenThrow(new IOException())
.thenThrow(new IOException())
.thenReturn(updateTemplate);
Thread.sleep(1500);
health = elkService.checkHealth();
Assert.assertEquals( Status.DOWN, health.getStatus());
Thread.sleep(3000);
health = elkService.checkHealth();
Assert.assertEquals(Status.UP, health.getStatus());
elkService.shutDown();
}
}

View File

@@ -0,0 +1,139 @@
package uk.co.gresearch.nortem.configeditor.service.nikita;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.nikita.common.NikitaAttributes;
import uk.co.gresearch.nortem.nikita.common.NikitaResult;
import uk.co.gresearch.nortem.nikita.compiler.NikitaCompiler;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
public class NikitaRuleSchemaServiceImplTest {
private NikitaRuleSchemaServiceImpl nikitaRuleSchemaService;
private final String ruleSchema = "dummmy schema";
private final String testRule = "dummmy rule";
private final String testRules = "dummmy rules";
private final String testEvent = "dummy event";
private final String testResultOutput = "test output";
private NikitaCompiler nikitaCompiler;
private NikitaResult nikitaResult;
private NikitaAttributes nikitaAttributes;
@Before
public void Setup() {
nikitaCompiler = Mockito.mock(NikitaCompiler.class);
this.nikitaRuleSchemaService = new NikitaRuleSchemaServiceImpl(nikitaCompiler, ruleSchema);
nikitaAttributes = new NikitaAttributes();
nikitaResult = new NikitaResult(NikitaResult.StatusCode.OK, nikitaAttributes);
Mockito.when(nikitaCompiler.validateRules(any())).thenReturn(nikitaResult);
Mockito.when(nikitaCompiler.validateRule(any())).thenReturn(nikitaResult);
Mockito.when(nikitaCompiler.testRule(testRule, testEvent)).thenReturn(nikitaResult);
Mockito.when(nikitaCompiler.testRule(testRules, testEvent)).thenReturn(nikitaResult);
}
@Test
public void getRulesSchemaOK() {
ConfigEditorResult ret = nikitaRuleSchemaService.getSchema();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(ret.getAttributes().getRulesSchema(), ruleSchema);
}
@Test
public void validateRulesOK() {
ConfigEditorResult ret = nikitaRuleSchemaService.validateConfigurations(testRules);
verify(nikitaCompiler, times(1)).validateRules(testRules);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void validateRulesError() {
ConfigEditorResult ret = nikitaRuleSchemaService.validateConfigurations(testRules);
verify(nikitaCompiler, times(1)).validateRules(testRules);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void validateRuleOK() {
ConfigEditorResult ret = nikitaRuleSchemaService.validateConfiguration(testRule);
verify(nikitaCompiler, times(1)).validateRule(testRule);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void ValidateRulesError() {
nikitaAttributes.setMessage("error");
nikitaAttributes.setException("exception");
nikitaResult = new NikitaResult(NikitaResult.StatusCode.ERROR, nikitaAttributes);
Mockito.when(nikitaCompiler.validateRules(any())).thenReturn(nikitaResult);
ConfigEditorResult ret = nikitaRuleSchemaService.validateConfigurations(testRules);
verify(nikitaCompiler, times(1)).validateRules(testRules);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
Assert.assertEquals("exception", ret.getAttributes().getException());
}
@Test
public void ValidateRuleError() {
nikitaAttributes.setMessage("error");
nikitaAttributes.setException("exception");
nikitaResult = new NikitaResult(NikitaResult.StatusCode.ERROR, nikitaAttributes);
Mockito.when(nikitaCompiler.validateRule(any())).thenReturn(nikitaResult);
ConfigEditorResult ret = nikitaRuleSchemaService.validateConfiguration(testRule);
verify(nikitaCompiler, times(1)).validateRule(testRule);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
Assert.assertEquals("exception", ret.getAttributes().getException());
}
@Test
public void TestRuleOK() {
nikitaAttributes.setMessage(testResultOutput);
ConfigEditorResult ret = nikitaRuleSchemaService.testConfiguration(testRule, testEvent);
verify(nikitaCompiler, times(1)).testRule(testRule, testEvent);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getTestResultComplete());
Assert.assertEquals(testResultOutput, ret.getAttributes().getTestResultOutput());
}
@Test
public void TestRulesError() {
nikitaAttributes.setMessage("error");
nikitaAttributes.setException("exception");
nikitaResult = new NikitaResult(NikitaResult.StatusCode.ERROR, nikitaAttributes);
Mockito.when(nikitaCompiler.testRules(testRules, testEvent)).thenReturn(nikitaResult);
ConfigEditorResult ret = nikitaRuleSchemaService.testConfigurations(testRules, testEvent);
verify(nikitaCompiler, times(1)).testRules(testRules, testEvent);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
Assert.assertEquals("exception", ret.getAttributes().getException());
}
@Test
public void TestRuleError() {
nikitaAttributes.setMessage("error");
nikitaAttributes.setException("exception");
nikitaResult = new NikitaResult(NikitaResult.StatusCode.ERROR, nikitaAttributes);
Mockito.when(nikitaCompiler.testRule(testRule, testEvent)).thenReturn(nikitaResult);
ConfigEditorResult ret = nikitaRuleSchemaService.testConfiguration(testRule, testEvent);
verify(nikitaCompiler, times(1)).testRule(testRule, testEvent);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
Assert.assertEquals("exception", ret.getAttributes().getException());
}
@Test
public void TestRuleInternalError() {
nikitaResult = new NikitaResult(NikitaResult.StatusCode.OK, nikitaAttributes);
Mockito.when(nikitaCompiler.testRule(testRule, testEvent)).thenReturn(nikitaResult);
ConfigEditorResult ret = nikitaRuleSchemaService.testConfiguration(testRule, testEvent);
verify(nikitaCompiler, times(1)).testRule(testRule, testEvent);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
}
}

View File

@@ -0,0 +1,217 @@
package uk.co.gresearch.nortem.configeditor.service.parserconfig;
import org.adrianwalker.multilinestring.Multiline;
import org.junit.Assert;
import org.junit.Test;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfoProvider;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorFile;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfo;
import java.util.ArrayList;
import java.util.List;
public class ParserConfigConfigInfoProviderTest {
/**
* {
* "parser_name": "test_parser",
* "parser_author": "john",
* "parser_version": 12345,
* "parser_config": {
* "parser_attributes": {
* "parser_type": "syslog",
* "syslog_config": {
* "syslog_version": "RFC_3164",
* "timezone": "UTC"
* }
* }
* }
* }
**/
@Multiline
public static String testParser;
/**
* {
* "parser_name": "test_parser",
* "parser_author": "john",
* "parser_version": 0,
* "parser_config": {
* "parser_attributes": {
* "parser_type": "syslog",
* "syslog_config": {
* "syslog_version": "RFC_3164",
* "timezone": "UTC"
* }
* }
* }
* }
**/
@Multiline
public static String testNewParser;
/**
* {
* "parsers_version" : 1,
* "parser_configurations": [
* {
* "parser_name": "test_parser",
* "parser_author": "john",
* "parser_version": 1,
* "parser_config": {
* "parser_attributes": {
* "parser_type": "syslog",
* "syslog_config": {
* "syslog_version": "RFC_3164",
* "timezone": "UTC"
* }
* }
* }
* }]
* }
**/
@Multiline
public static String release;
/**
* {
* "parser_name": "../../../test_parser",
* "parser_author": "john",
* "parser_version": 12345,
* "parser_config": {
* "parser_attributes": {
* "parser_type": "syslog",
* "syslog_config": {
* "syslog_version": "RFC_3164",
* "timezone": "UTC"
* }
* }
* }
* }
**/
@Multiline
public static String maliciousConfig;
public static String user = "steve@secret.net";
private final ConfigInfoProvider infoProvider = ParserConfigConfigInfoProvider.create();
@Test
public void ConfigInfoTestChangeAuthor() {
ConfigInfo info = infoProvider.getConfigInfo(user, testParser);
Assert.assertEquals(12345, info.getOldVersion());
Assert.assertEquals(12346, info.getVersion());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals("Updating configuration: test_parser to version: 12346", info.getCommitMessage());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals(info.getCommitterEmail(), user);
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test_parser.json"));
Assert.assertTrue(info.getFilesContent()
.get("test_parser.json").indexOf("\"parser_version\": 12346,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("test_parser.json").indexOf("\"parser_author\": \"steve\",") > 0);
Assert.assertFalse(info.isNewConfig());
}
@Test
public void ConfigInfoTestUnchangedAuthor() {
ConfigInfo info = infoProvider.getConfigInfo("john@secret.net", testParser);
Assert.assertEquals(12345, info.getOldVersion());
Assert.assertEquals("john", info.getCommitter());
Assert.assertEquals("Updating configuration: test_parser to version: 12346", info.getCommitMessage());
Assert.assertEquals("john@secret.net", info.getCommitterEmail());
Assert.assertEquals( 1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test_parser.json"));
Assert.assertTrue(info.getFilesContent()
.get("test_parser.json").indexOf("\"parser_version\": 12346,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("test_parser.json").indexOf("\"parser_author\": \"john\",") > 0);
Assert.assertFalse(info.isNewConfig());
}
@Test
public void ConfigInfoNewRule() {
ConfigInfo info = infoProvider.getConfigInfo(user, testNewParser);
Assert.assertEquals(0, info.getOldVersion());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals("Adding new configuration: test_parser", info.getCommitMessage());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test_parser.json"));
Assert.assertTrue(info.getFilesContent()
.get("test_parser.json").indexOf("\"parser_version\": 1,") > 0);
Assert.assertTrue(info.isNewConfig());
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ConfigInfoWrongJson() {
infoProvider.getConfigInfo(user,"WRONG JSON");
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ConfigInfoWrongMissingMetadata() {
infoProvider.getConfigInfo(user, maliciousConfig);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ConfigInfoWrongUser() {
infoProvider.getConfigInfo("INVALID", testParser);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ReleaseInfoWrongUser() {
infoProvider.getReleaseInfo("INVALID", testParser);
}
@Test
public void ReleaseTest() {
ConfigInfo info = infoProvider.getReleaseInfo("steve@secret.net", release);
Assert.assertEquals(1, info.getOldVersion());
Assert.assertEquals(2, info.getVersion());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals("Configuration released to version: 2", info.getCommitMessage());
Assert.assertEquals("steve", info.getCommitter());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("parsers.json"));
Assert.assertTrue(info.getFilesContent()
.get("parsers.json").indexOf("\"parsers_version\": 2,") > 0);
}
@Test
public void FilterRulesTest() {
Assert.assertFalse(infoProvider.isReleaseFile("a.json"));
Assert.assertTrue(infoProvider.isReleaseFile("parsers.json"));
Assert.assertTrue(infoProvider.isStoreFile("abc.json"));
Assert.assertFalse(infoProvider.isStoreFile("json.txt"));
}
@Test
public void RulesVersionTest() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("parsers.json", release, ConfigEditorFile.ContentType.RAW_JSON_STRING));
int version = infoProvider.getReleaseVersion(files);
Assert.assertEquals(1, version);
}
@Test(expected = IllegalArgumentException.class)
public void RulesVersionTestMissingFile() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("a.json", release, ConfigEditorFile.ContentType.RAW_JSON_STRING));
infoProvider.getReleaseVersion(files);
}
@Test(expected = IllegalArgumentException.class)
public void RulesVersionMissingVersion() {
List<ConfigEditorFile> files = new ArrayList<>();
files.add(new ConfigEditorFile("parsers.json",
"{}",
ConfigEditorFile.ContentType.RAW_JSON_STRING));
infoProvider.getReleaseVersion(files);
}
}

View File

@@ -0,0 +1,139 @@
package uk.co.gresearch.nortem.configeditor.service.parserconfig;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.parsers.common.ParserResult;
import uk.co.gresearch.nortem.parsers.factory.ParserFactory;
import uk.co.gresearch.nortem.parsers.factory.ParserFactoryAttributes;
import uk.co.gresearch.nortem.parsers.factory.ParserFactoryResult;
import java.util.ArrayList;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
public class ParserConfigSchemaServiceImplTest {
private ParserConfigSchemaServiceImpl parserConfigSchemaService;
private final String schema = "dummmy schema";
private final String testConfig = "dummmy parser config";
private final String testConfigs = "dummmy parser configs";
private final String testLog = "dummy log";
private final String testLogSpecification = "{ \"log\" : \"dummy log\"}";
private ParserFactory parserFactory;
private ParserFactoryResult parserFactoryResult;
private ParserFactoryAttributes parserFactoryAttributes;
private ParserResult parserResult;
@Before
public void Setup() {
parserFactory = Mockito.mock(ParserFactory.class);
this.parserConfigSchemaService = new ParserConfigSchemaServiceImpl(parserFactory, schema);
parserFactoryAttributes = new ParserFactoryAttributes();
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.OK, parserFactoryAttributes);
parserResult = new ParserResult();
Mockito.when(parserFactory.create(any())).thenReturn(parserFactoryResult);
Mockito.when(parserFactory.validateConfiguration(any())).thenReturn(parserFactoryResult);
Mockito.when(parserFactory.validateConfigurations(any())).thenReturn(parserFactoryResult);
Mockito.when(parserFactory.test(any(), any())).thenReturn(parserFactoryResult);
}
@Test
public void getSchemaOK() {
ConfigEditorResult ret = parserConfigSchemaService.getSchema();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(schema, ret.getAttributes().getRulesSchema());
}
@Test
public void validateConfigurationsOK() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parserFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void validateConfigurationsError() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parserFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void validateConfigurationOK() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfiguration(testConfig);
Mockito.verify(parserFactory, times(1)).validateConfiguration(testConfig);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void ValidateRulesError() {
parserFactoryAttributes.setMessage("error");
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.ERROR, parserFactoryAttributes);
Mockito.when(parserFactory.validateConfigurations(any())).thenReturn(parserFactoryResult);
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parserFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
}
@Test
public void ValidateRuleError() {
parserFactoryAttributes.setMessage("error");
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.ERROR, parserFactoryAttributes);
Mockito.when(parserFactory.validateConfiguration(any())).thenReturn(parserFactoryResult);
ConfigEditorResult ret = parserConfigSchemaService.validateConfiguration(testConfig);
Mockito.verify(parserFactory, times(1)).validateConfiguration(testConfig);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
}
@Test
public void TestConfigurationOK() {
parserResult.setParsedMessages(new ArrayList<>());
parserFactoryAttributes.setParserResult(parserResult);
ConfigEditorResult ret = parserConfigSchemaService.testConfiguration(testConfig, testLogSpecification);
Mockito.verify(parserFactory, times(1)).test(testConfig, testLog.getBytes());
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getTestResultComplete());
}
@Test
public void TestConfigurationsError() {
parserFactoryAttributes.setMessage("error");
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.ERROR, parserFactoryAttributes);
Mockito.when(parserFactory.test(testConfigs, testLog.getBytes())).thenReturn(parserFactoryResult);
ConfigEditorResult ret = parserConfigSchemaService.testConfigurations(testConfigs, testLog);
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("Not implemented", ret.getAttributes().getMessage());
}
@Test
public void TestConfigurationError() {
parserFactoryAttributes.setMessage("error");
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.ERROR, parserFactoryAttributes);
Mockito.when(parserFactory.test(testConfig, testLog.getBytes())).thenReturn(parserFactoryResult);
ConfigEditorResult ret = parserConfigSchemaService.testConfiguration(testConfig, testLogSpecification);
Mockito.verify(parserFactory, times(1)).test(testConfig, testLog.getBytes());
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertEquals("error", ret.getAttributes().getMessage());
}
@Test
public void TestConfigurationParserResultError() {
parserResult.setException(new IllegalStateException("dummy"));
parserFactoryAttributes.setParserResult(parserResult);
parserFactoryResult = new ParserFactoryResult(ParserFactoryResult.StatusCode.OK, parserFactoryAttributes);
Mockito.when(parserFactory.test(testConfig, testLog.getBytes())).thenReturn(parserFactoryResult);
ConfigEditorResult ret = parserConfigSchemaService.testConfiguration(testConfig, testLogSpecification);
Mockito.verify(parserFactory, times(1)).test(testConfig, testLog.getBytes());
Assert.assertEquals(ConfigEditorResult.StatusCode.ERROR, ret.getStatusCode());
Assert.assertTrue(ret.getAttributes().getMessage().contains("dummy"));
}
}

View File

@@ -0,0 +1,195 @@
package uk.co.gresearch.nortem.configeditor.service.parsingapp;
import org.adrianwalker.multilinestring.Multiline;
import org.junit.Assert;
import org.junit.Test;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfo;
import uk.co.gresearch.nortem.configeditor.configstore.ConfigInfoProvider;
public class ParsingAppConfigInfoProviderTest {
/**
*{
* "parsing_app_name": "test",
* "parsing_app_version": 12345,
* "parsing_app_author": "dummy",
* "parsing_app_description": "Description of parser application",
* "parsing_app_settings": {
* "input_topics": [
* "secret"
* ],
* "error_topic": "error",
* "input_parallelism": 1,
* "parsing_parallelism": 2,
* "output_parallelism": 3,
* "parsing_app_type": "single_parser"
* },
* "parsing_settings": {
* "single_parser": {
* "parser_name": "single",
* "output_topic": "output"
* }
* }
* }
**/
@Multiline
static String simpleSingleApplicationParser;
/**
*{
* "parsing_app_name": "test",
* "parsing_app_version": 0,
* "parsing_app_author": "dummy",
* "parsing_app_description": "Description of parser application",
* "parsing_app_settings": {
* "input_topics": [
* "secret"
* ],
* "error_topic": "error",
* "input_parallelism": 1,
* "parsing_parallelism": 2,
* "output_parallelism": 3,
* "parsing_app_type": "single_parser"
* },
* "parsing_settings": {
* "single_parser": {
* "parser_name": "single",
* "output_topic": "output"
* }
* }
* }
**/
@Multiline
static String simpleSingleApplicationParserNew;
/**
*{
* "parsing_applications_version" : 1,
* "parsing_applications" : [
* {
* "parsing_app_name": "test",
* "parsing_app_version": 12345,
* "parsing_app_author": "dummy",
* "parsing_app_description": "Description of parser application",
* "parsing_app_settings": {
* "input_topics": [
* "secret"
* ],
* "error_topic": "error",
* "input_parallelism": 1,
* "parsing_parallelism": 2,
* "output_parallelism": 3,
* "parsing_app_type": "single_parser"
* },
* "parsing_settings": {
* "single_parser": {
* "parser_name": "single",
* "output_topic": "output"
* }
* }
* }
* ]
* }
*
**/
@Multiline
static String release;
static String user = "unknown@secret.net";
private final ConfigInfoProvider infoProvider = ParsingAppConfigInfoProvider.create();
@Test
public void ConfigInfoTestChangeAuthor() {
ConfigInfo info = infoProvider.getConfigInfo(user, simpleSingleApplicationParser);
Assert.assertEquals(12345, info.getOldVersion());
Assert.assertEquals(12346, info.getVersion());
Assert.assertEquals("unknown", info.getCommitter());
Assert.assertEquals("Updating configuration: test to version: 12346", info.getCommitMessage());
Assert.assertEquals("unknown", info.getCommitter());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test.json"));
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_version\": 12346,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_author\": \"unknown\",") > 0);
Assert.assertFalse(info.isNewConfig());
}
@Test
public void ConfigInfoTestUnchangedAuthor() {
ConfigInfo info = infoProvider.getConfigInfo("dummy@secret.net", simpleSingleApplicationParser);
Assert.assertEquals(12345, info.getOldVersion());
Assert.assertEquals("dummy", info.getCommitter());
Assert.assertEquals("Updating configuration: test to version: 12346", info.getCommitMessage());
Assert.assertEquals("dummy@secret.net", info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test.json"));
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_version\": 12346,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_author\": \"dummy\",") > 0);
Assert.assertFalse(info.isNewConfig());
}
@Test
public void ConfigInfoNewRule() {
ConfigInfo info = infoProvider.getConfigInfo(user, simpleSingleApplicationParserNew);
Assert.assertEquals(0, info.getOldVersion());
Assert.assertEquals("unknown", info.getCommitter());
Assert.assertEquals("Adding new configuration: test", info.getCommitMessage());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("test.json"));
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_version\": 1,") > 0);
Assert.assertTrue(info.getFilesContent()
.get("test.json").indexOf("\"parsing_app_author\": \"unknown\",") > 0);
Assert.assertTrue(info.isNewConfig());
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ConfigInfoWrongJson() {
infoProvider.getConfigInfo(user, "WRONG JSON");
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ConfigInfoWrongUser() {
infoProvider.getConfigInfo("INVALID", simpleSingleApplicationParser);
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void ReleaseInfoWrongUser() {
infoProvider.getReleaseInfo("INVALID", simpleSingleApplicationParser);
}
@Test
public void ReleaseTest() {
ConfigInfo info = infoProvider.getReleaseInfo("unknown@secret.net", release.trim());
Assert.assertEquals(1, info.getOldVersion());
Assert.assertEquals(2, info.getVersion());
Assert.assertEquals("unknown", info.getCommitter());
Assert.assertEquals("Configuration released to version: 2", info.getCommitMessage());
Assert.assertEquals("unknown", info.getCommitter());
Assert.assertEquals(user, info.getCommitterEmail());
Assert.assertEquals(1, info.getFilesContent().size());
Assert.assertTrue(info.getFilesContent().containsKey("parsing_applications.json"));
Assert.assertTrue(info.getFilesContent()
.get("parsing_applications.json").indexOf("\"parsing_applications_version\": 2,") > 0);
}
@Test
public void FilterRulesTest() {
Assert.assertFalse(infoProvider.isReleaseFile("a.json"));
Assert.assertTrue(infoProvider.isReleaseFile("parsing_applications.json"));
Assert.assertTrue(infoProvider.isStoreFile("abc.json"));
Assert.assertFalse(infoProvider.isStoreFile("json.txt"));
}
}

View File

@@ -0,0 +1,90 @@
package uk.co.gresearch.nortem.configeditor.service.parsingapp;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import uk.co.gresearch.nortem.configeditor.model.ConfigEditorResult;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactory;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryAttributes;
import uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryResult;
import uk.co.gresearch.nortem.parsers.common.ParserResult;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryResult.StatusCode.ERROR;
import static uk.co.gresearch.nortem.parsers.application.factory.ParsingApplicationFactoryResult.StatusCode.OK;
public class ParsingAppConfigSchemaServiceImplTest {
private ParsingAppConfigSchemaServiceImpl parserConfigSchemaService;
private final String schema = "dummmy schema";
private final String testConfig = "dummmy config";
private final String testConfigs = "dummmy configs";
private ParsingApplicationFactory parsingAppFactory;
private ParsingApplicationFactoryResult factoryResult;
private ParsingApplicationFactoryAttributes factoryAttributes;
private ParserResult parsingAppResult;
@Before
public void Setup() {
parsingAppFactory = Mockito.mock(ParsingApplicationFactory.class);
this.parserConfigSchemaService = new ParsingAppConfigSchemaServiceImpl(parsingAppFactory, schema);
factoryAttributes = new ParsingApplicationFactoryAttributes();
factoryResult = new ParsingApplicationFactoryResult(OK, factoryAttributes);
parsingAppResult = new ParserResult();
Mockito.when(parsingAppFactory.validateConfiguration(any())).thenReturn(factoryResult);
Mockito.when(parsingAppFactory.validateConfigurations(any())).thenReturn(factoryResult);
}
@Test
public void getSchemaOK() {
factoryAttributes.setJsonSchema(schema);
ConfigEditorResult ret = parserConfigSchemaService.getSchema();
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
Assert.assertEquals(schema, ret.getAttributes().getRulesSchema());
}
@Test
public void validateConfigurationsOK() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parsingAppFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void validateConfigurationsError() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parsingAppFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ret.getStatusCode(), ConfigEditorResult.StatusCode.OK);
}
@Test
public void validateConfigurationOK() {
ConfigEditorResult ret = parserConfigSchemaService.validateConfiguration(testConfig);
Mockito.verify(parsingAppFactory, times(1)).validateConfiguration(testConfig);
Assert.assertEquals(ConfigEditorResult.StatusCode.OK, ret.getStatusCode());
}
@Test
public void ValidateConfigsError() {
factoryAttributes.setMessage("error");
factoryResult = new ParsingApplicationFactoryResult(ERROR, factoryAttributes);
Mockito.when(parsingAppFactory.validateConfigurations(any())).thenReturn(factoryResult);
ConfigEditorResult ret = parserConfigSchemaService.validateConfigurations(testConfigs);
Mockito.verify(parsingAppFactory, times(1)).validateConfigurations(testConfigs);
Assert.assertEquals(ret.getStatusCode(), ConfigEditorResult.StatusCode.ERROR);
Assert.assertEquals(ret.getAttributes().getMessage(), "error");
}
@Test
public void ValidateCongError() {
factoryAttributes.setMessage("error");
factoryResult = new ParsingApplicationFactoryResult(ERROR, factoryAttributes);
Mockito.when(parsingAppFactory.validateConfiguration(any())).thenReturn(factoryResult);
ConfigEditorResult ret = parserConfigSchemaService.validateConfiguration(testConfig);
Mockito.verify(parsingAppFactory, times(1)).validateConfiguration(testConfig);
Assert.assertEquals(ret.getStatusCode(), ConfigEditorResult.StatusCode.ERROR);
Assert.assertEquals(ret.getAttributes().getMessage(), "error");
}
}

View File

@@ -0,0 +1,96 @@
{
"statusCode": "OK",
"attributes": {
"fields": [
{
"name": "centrifuge:id",
"type": "string"
},
{
"name": "centrifuge:waiting_ms",
"type": "integer"
},
{
"name": "centrifuge:original_msg",
"type": "string"
},
{
"suffix_required": true,
"name": "centrifuge:elk_search:count",
"type": "integer"
},
{
"suffix_required": true,
"name": "centrifuge:elk_search:result",
"type": "json_array"
},
{
"suffix_required": true,
"name": "centrifuge:array_reducer",
"type": "string"
},
{
"name": "centrifuge:hive_alert:id",
"type": "string"
},
{
"name": "centrifuge:hive_alert:created_at",
"type": "integer"
},
{
"name": "centrifuge:hive_case:id",
"type": "string"
},
{
"name": "centrifuge:hive_case:case_id",
"type": "string"
},
{
"name": "centrifuge:hive_case:created_at",
"type": "integer"
},
{
"name": "centrifuge:ad_groups:user_info",
"type": "string"
},
{
"suffix_required": true,
"name": "centrifuge:ad_groups:membership",
"type": "string"
},
{
"name": "centrifuge:jira_issue:id",
"type": "string"
},
{
"name": "centrifuge:jira_issue:url",
"type": "string"
},
{
"suffix_required": true,
"name": "centrifuge:jira_search:count",
"type": "integer"
},
{
"suffix_required": true,
"name": "centrifuge:jira_search:result",
"type": "json_array"
},
{
"suffix_required": true,
"name": "centrifuge:cortex_analysis:value",
"type": "string"
},
{
"suffix_required": true,
"name": "centrifuge:cortex_analysis:level",
"type": "string"
},
{
"suffix_required": true,
"name": "centrifuge:cortex_analysis:full_report",
"type": "json_object"
}
]
}
}

View File

@@ -0,0 +1,116 @@
{
"dummy1": {
"order": 0,
"template": "nortem-dummy1*",
"settings": {
"index": {
"routing": {
"allocation": {
"total_shards_per_node": "1"
}
},
"mapping": {
"ignore_malformed": "true"
},
"refresh_interval": "1s",
"number_of_shards": "3",
"merge": {
"scheduler": {
"max_thread_count": "1"
}
}
}
},
"mappings": {
"secretlogs_doc": {
"dynamic_templates": [
{
"timestamps": {
"match": "*:time",
"match_mapping_type": "*",
"mapping": {
"type": "date",
"format": "epoch_millis"
}
}
}
],
"properties": {
"security:category": {
"type": "keyword"
},
"security:level": {
"type": "keyword"
},
"ip_src_addr": {
"type": "ip"
},
"ip_dst_addr": {
"type": "ip"
},
"ip_dst_port": {
"type": "integer"
},
"ip_src_port": {
"type": "integer"
}
}
}
},
"aliases": {}
},
"dummy2": {
"order": 0,
"template": "nortem-dummy2*",
"settings": {
"index": {
"routing": {
"allocation": {
"total_shards_per_node": "1"
}
},
"mapping": {
"ignore_malformed": "true"
},
"refresh_interval": "1s",
"number_of_shards": "3",
"merge": {
"scheduler": {
"max_thread_count": "1"
}
}
}
},
"mappings": {
"plaintext_doc": {
"dynamic_templates": [
{
"timestamps": {
"match": "*:time",
"match_mapping_type": "*",
"mapping": {
"type": "date",
"format": "epoch_millis"
}
}
}
],
"properties": {
"dlp:category": {
"type": "keyword"
},
"dlp:level": {
"type": "keyword"
},
"ip_src_addr": {
"type": "ip"
},
"ip_dst_port": {
"type": "integer"
}
}
}
},
"aliases": {}
}
}

View File

@@ -0,0 +1,768 @@
{
"statusCode": "OK",
"attributes": {
"rules_schema": {
"type": "object",
"description": "Centrifuge Rules",
"title": "rules",
"properties": {
"rules_version": {
"type": "integer",
"description": "Centrifuge rules version",
"default": 0
},
"rules": {
"type": "array",
"items": {
"type": "object",
"description": "Centrifuge rule that should handle response to AVA alert",
"title": "rule",
"properties": {
"rule_name": {
"type": "string",
"description": "Rule name that uniquely identifies the rule"
},
"rule_author": {
"type": "string",
"description": "The owner of the rule"
},
"rule_version": {
"type": "integer",
"description": "The version of the rule",
"default": 0
},
"rule_description": {
"type": "string",
"description": "The description of the rule"
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"description": "Field pattern used to match patterns on the field",
"title": "field pattern",
"properties": {
"field_name": {
"type": "string",
"description": "The field on which the pattern is applied"
},
"field_pattern": {
"type": "string",
"description": "Regular expression pattern applied on the field"
}
},
"required": [
"field_name",
"field_pattern"
]
},
"description": "Conditions required for evaluating the rule",
"minItems": 1
},
"enrichments": {
"type": "object",
"description": "Enrichments applied for the alert",
"title": "enrichments",
"properties": {
"elk_search": {
"type": "array",
"items": {
"type": "object",
"description": "Elk query can specify an elk query and further processing of the result",
"title": "elk query",
"properties": {
"name": {
"type": "string",
"description": "Name of the query"
},
"index": {
"type": "string",
"description": "The index used in the query"
},
"query_type": {
"type": "string",
"description": "The query type",
"enum": [
"lucene",
"json"
],
"default": "json"
},
"query": {
"type": "string",
"description": "The elk search query"
},
"source": {
"type": "array",
"items": {
"type": "string"
},
"description": "Requested fields in elk query result. All fields will be used if not provided",
"minItems": 1
},
"size": {
"type": "integer",
"description": "The maximum size of the query result",
"maximum": 500,
"minimum": 1,
"default": 100
},
"sort": {
"type": "array",
"items": {
"type": "object",
"description": "Sort specifies the order of the elk query result",
"title": "sort",
"properties": {
"field_name": {
"type": "string",
"description": "The field name used for sorting"
},
"order": {
"type": "string",
"description": "The order of sorting",
"enum": [
"asc",
"desc"
]
}
},
"required": [
"field_name",
"order"
]
},
"description": "The sort of the result, sorted by timestamp/desc if not provided",
"minItems": 1
},
"reducers": {
"type": "array",
"items": {
"type": "object",
"description": "The array reducer reduces a query result from an array to a single field",
"title": "array reducer",
"properties": {
"name": {
"type": "string",
"description": "The name of the reducer"
},
"type": {
"type": "string",
"description": "The type of the reducer",
"enum": [
"first",
"concatenate"
]
},
"field": {
"type": "string",
"description": "The field from the query result on which the reducer will be applied"
}
},
"required": [
"field",
"name",
"type"
]
},
"description": "List of reducers specified individually",
"minItems": 1
},
"all_sources_reducer_first": {
"type": "boolean",
"description": "First reducers will be generated for all fields from the source list",
"default": false
},
"all_sources_reducer_concatenate": {
"type": "boolean",
"description": "Concatenate reducers will be generated for all fields from the source list",
"default": false
},
"required_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of fields of the alert required for evaluating the query. The query is skipped if a field is missing",
"minItems": 1
}
},
"required": [
"index",
"query",
"name"
]
},
"description": "The list of elk search queries",
"minItems": 1
},
"jira_search": {
"type": "array",
"items": {
"type": "object",
"description": "Jira query can specify a jira query and further processing of the result",
"title": "jira query",
"properties": {
"name": {
"type": "string",
"description": "Name of the query"
},
"query": {
"type": "string",
"description": "Query in JQL - Jira Query Language"
},
"fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields required in the response",
"minItems": 1
},
"max_result": {
"type": "integer",
"description": "Max result of the response",
"default": 50
},
"reducers": {
"type": "array",
"items": {
"type": "object",
"description": "The array reducer reduces a query result from an array to a single field",
"title": "array reducer",
"properties": {
"name": {
"type": "string",
"description": "The name of the reducer"
},
"type": {
"type": "string",
"description": "The type of the reducer",
"enum": [
"first",
"concatenate"
]
},
"field": {
"type": "string",
"description": "The field from the query result on which the reducer will be applied"
}
},
"required": [
"field",
"name",
"type"
]
},
"description": "List of reducers specified individually",
"minItems": 1
},
"all_fields_reducer_first": {
"type": "boolean",
"description": "First reducers will be generated for all specific fields from the fields list",
"default": false
},
"all_fields_reducer_concatenate": {
"type": "boolean",
"description": "Concatenate reducers will be generated for all specific fields from the source list",
"default": false
},
"required_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of alert fields required for evaluating the query. The query is skipped if a field is missing",
"minItems": 1
}
},
"required": [
"fields",
"max_result",
"query",
"name"
]
},
"description": "The list of jira search queries",
"minItems": 1
},
"cortex_analysis": {
"type": "array",
"items": {
"type": "object",
"description": "Cortex analysis enrichment that runs the cortex analyzer and waits for the report",
"title": "cortex analysis",
"properties": {
"name": {
"type": "string",
"description": "Report name that should identify the analysis"
},
"analyzer": {
"type": "string",
"description": "The name of the analyser - without version"
},
"force_cache": {
"type": "boolean",
"description": "Force the cache of the analyser and re-run the analysis",
"default": false
},
"wait_time": {
"type": "integer",
"description": "Maximum time to wait for analysis in minutes",
"maximum": 5,
"minimum": 1,
"default": 1
},
"data_type": {
"type": "string",
"description": "Type of the data for analysis",
"enum": [
"url",
"ip",
"domain",
"fqdn"
]
},
"data": {
"type": "string",
"description": "Content of the observable"
},
"message": {
"type": "string",
"description": "Description of the item of analysis"
},
"tlp": {
"type": "integer",
"description": "Traffic Light Protocol 0: white, 1: green, 2: amber, 3: red",
"maximum": 3,
"minimum": 0,
"default": 2
}
},
"required": [
"analyzer",
"data",
"data_type",
"name"
]
},
"description": "The list of cortex analysis",
"minItems": 1
},
"ad_groups_enrichment": {
"type": "object",
"description": "AD user info and group membership queries",
"title": "ad groups",
"properties": {
"user_name": {
"type": "string",
"description": "The username from the alert - it should be a variable",
"pattern": "^\\$\\{\\w+\\}$"
},
"compute_user_info": {
"type": "boolean",
"description": "Request AD info about the user",
"default": false
},
"membership_queries": {
"type": "array",
"items": {
"type": "object",
"description": "Predicate for user membership to AD groups",
"title": "AD groups membership query",
"properties": {
"name": {
"type": "string",
"description": "The name of the query"
},
"predicate": {
"type": "string",
"description": "The type of the predicate",
"enum": [
"isMemberOfAny",
"IsMemberOfAll"
]
},
"ad_groups": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of groups",
"minItems": 1
}
},
"required": [
"ad_groups",
"name",
"predicate"
]
},
"description": "Queries about user AD group membership"
}
},
"required": [
"user_name"
]
},
"wait_in_ms": {
"type": "integer",
"description": "Time in milliseconds the rule will wait before being evaluated"
}
}
},
"filters": {
"type": "array",
"items": {
"type": "object",
"description": "Field pattern used to match patterns on the field",
"title": "field pattern",
"properties": {
"field_name": {
"type": "string",
"description": "The field on which the pattern is applied"
},
"field_pattern": {
"type": "string",
"description": "Regular expression pattern applied on the field"
}
},
"required": [
"field_name",
"field_pattern"
]
},
"description": "Filters applied to the execution of rule actions",
"minItems": 1
},
"actions": {
"type": "object",
"description": "Actions to be performed as a response to the alert",
"title": "actions",
"properties": {
"create_the_hive_alert": {
"type": "object",
"description": "Create the hive alert",
"title": "the hive alert",
"properties": {
"title": {
"type": "string",
"description": "Title of the alert"
},
"type": {
"type": "string",
"description": "Type of the alert"
},
"source": {
"type": "string",
"description": "Source of the alert"
},
"status": {
"type": "string",
"enum": [
"New",
"Updated",
"Ignored",
"Imported"
],
"default": "New"
},
"severity": {
"type": "integer",
"maximum": 3,
"minimum": 1,
"default": 2
},
"tlp": {
"type": "integer",
"description": "Traffic Light Protocol 0: white, 1: green, 2: amber, 3: red",
"maximum": 3,
"minimum": 0,
"default": 2
},
"caseTemplate": {
"type": "string",
"description": "Case template to use when a case is created from this alert"
},
"artifacts": {
"type": "array",
"items": {
"type": "object",
"description": "The hive alert artifact - observable",
"title": "the hive alert artifact",
"properties": {
"dataType": {
"type": "string",
"description": "Type of the observable",
"enum": [
"url",
"other",
"user-agent",
"regexp",
"mail_subject",
"registry",
"mail",
"domain",
"autonomous-system",
"ip",
"uri_path",
"filename",
"hash",
"file",
"fqdn",
"phone_number"
]
},
"data": {
"type": "string",
"description": "Content of the observable"
},
"message": {
"type": "string",
"description": "Description of the artifact in the context of the case"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "The tags of the observable"
},
"tlp": {
"type": "integer",
"description": "Traffic Light Protocol 0: white, 1: green, 2: amber, 3: red",
"maximum": 3,
"minimum": 0,
"default": 2
},
"ioc": {
"type": "boolean",
"description": "Indicator of Compromise",
"default": false
}
},
"required": [
"data",
"dataType",
"ioc"
]
},
"description": "Artifacts (Observables) of the alert"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "The tags of the alert"
},
"description": {
"type": "string",
"description": "Description of alert is used if markdown table is not provided"
}
},
"required": [
"source",
"title",
"type"
]
},
"create_the_hive_case": {
"type": "object",
"description": "Create the hive case",
"title": "the hive case",
"properties": {
"title": {
"type": "string",
"description": "Title of the case"
},
"severity": {
"type": "integer",
"description": "Severity of the case",
"maximum": 3,
"minimum": 1,
"default": 2
},
"tlp": {
"type": "integer",
"description": "Traffic Light Protocol 0: white, 1: green, 2: amber, 3: red",
"maximum": 3,
"minimum": 0,
"default": 2
},
"flag": {
"type": "boolean",
"description": "Flag case as important",
"default": false
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "The tags of the alert"
},
"description": {
"type": "string",
"description": "Description of alert. It is used if markdown table is not provided"
},
"owner": {
"type": "string",
"description": "The user to whom the case has been assigned"
}
},
"required": [
"title"
]
},
"the_hive_custom_fields": {
"type": "object",
"description": "Specification of the custom fields used in the hive actions",
"title": "field filter",
"properties": {
"including_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of regular expression patterns for including fields",
"minItems": 1,
"default": [
".*"
]
},
"excluding_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of regular expression patterns for excluding fields"
}
}
},
"the_hive_description_tables": {
"type": "array",
"items": {
"type": "object",
"description": "Markdown table used for formatting the description in the hive actions",
"title": "markdown table",
"properties": {
"table_name": {
"type": "string",
"description": "The name of the table"
},
"field": {
"type": "string",
"description": "The array field of the alert printed in the table"
},
"field_filter": {
"type": "object",
"description": "The field filters used for filtering the alert fields in the table",
"title": "field filter",
"properties": {
"including_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of regular expression patterns for including fields",
"minItems": 1,
"default": [
".*"
]
},
"excluding_fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of regular expression patterns for excluding fields"
}
},
"default": {
"including_fields": [
".*"
]
}
},
"suffix_name_only": {
"type": "boolean",
"description": "If the flag is set, the formatter will use the suffix of centrifuge fields",
"default": false
}
},
"required": [
"table_name"
]
},
"description": "List of the markdown tables used in the hive actions"
},
"create_jira_issue": {
"type": "object",
"description": "Create Jira issue",
"title": "create jira issue",
"properties": {
"project": {
"type": "string",
"description": "The project of the issue"
},
"type": {
"type": "string",
"description": "The type of the issue",
"enum": [
"Task",
"Query/Question",
"Misc",
"Bug"
]
},
"summary": {
"type": "string",
"description": "The summary of the issue"
},
"description": {
"type": "string",
"description": "The description of the issue"
},
"reporter": {
"type": "string",
"description": "The reporter of the issue"
},
"assignee": {
"type": "string",
"description": "The assignee of the issue"
}
},
"required": [
"project",
"summary",
"type"
]
},
"elk_store": {
"type": "boolean",
"description": "Store the enriched alert in the elk store",
"default": true
}
}
}
},
"required": [
"actions",
"enrichments",
"rule_author",
"rule_name",
"rule_version"
]
},
"description": "Centrifuge rules",
"minItems": 1
}
},
"required": [
"rules",
"rules_version"
]
}
}
}

View File

@@ -0,0 +1,17 @@
{
"layout": {
"the_hive_description_tables.excluding_fields": {
"type": "expansion-panel"
},
"conditions": {
"type": "card",
"options": {
"expandable": true,
"expanded": true
}
},
"create_the_hive_alert.artifacts.items": {
"type": "expansion-panel"
}
}
}

View File

@@ -0,0 +1 @@
"# config-editor-spring"

View File

@@ -1 +0,0 @@
"# config-editor"

38
config-editor/pom.xml Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>config-editor</artifactId>
<name>config-editor</name>
<packaging>pom</packaging>
<properties>
</properties>
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem</artifactId>
<version>1.07-SNAPSHOT</version>
</parent>
<modules>
<module>config-editor-core</module>
<module>config-editor-services</module>
</modules>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<source>${java_version}</source>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<target>${java_version}</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
@@ -17,6 +17,11 @@
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
@@ -109,13 +114,13 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<version>${mockito_version}</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,138 @@
package uk.co.gresearch.nortem.common.utils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandles;
import java.security.Principal;
import java.util.function.Supplier;
public class HttpProvider {
private static final Logger LOG = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private static final int TIMEOUT_MILLI = 120000;
private final String host;
private final Supplier<CloseableHttpClient> clientFactory;
public HttpProvider(String uri, Supplier<CloseableHttpClient> clientFactory) {
this.host = uri;
this.clientFactory = clientFactory;
}
private String readBody(InputStream input) throws IOException {
try (BufferedReader rd = new BufferedReader(
new InputStreamReader(input))) {
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
return result.toString();
}
}
public String get(String path) throws IOException {
try (CloseableHttpClient httpClient = clientFactory.get()) {
String url = host + path;
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
LOG.error(String.format("unsuccessful get request on url: %s return status line: %s",
url, response.getStatusLine().toString()));
throw new IOException("Unsuccessful GET");
}
return readBody(response.getEntity().getContent());
}
} catch (Exception e) {
LOG.error(String.format("Exception during executing GET request on url: %s%s",
host, path));
throw e;
}
}
public String post(String path, String jsonBody) throws IOException {
try (CloseableHttpClient httpClient = getKerberosHttpClient()) {
String url = host + path;
HttpPost httpPost = new HttpPost(url);
StringEntity requestEntity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON);
httpPost.setEntity(requestEntity);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED &&
response.getStatusLine().getStatusCode() != HttpStatus.SC_OK &&
response.getStatusLine().getStatusCode() != HttpStatus.SC_BAD_REQUEST) {
LOG.error(String.format(
"Unsuccessful post request on url: %s, req body %s, status line: %s",
url, jsonBody, response.getStatusLine().toString()));
throw new IOException("Unsuccessful POST");
}
return readBody(response.getEntity().getContent());
}
} catch (Exception e) {
LOG.error(String.format("Exception during executing POST request on url: %s%s\nbody: %s\nstack_trace: %s",
host, path, jsonBody, ExceptionUtils.getStackTrace(e)));
throw e;
}
}
public static CloseableHttpClient getKerberosHttpClient() {
Credentials useJaasCreds = new Credentials() {
public String getPassword() {
return null;
}
public Principal getUserPrincipal() {
return null;
}
};
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1, null), useJaasCreds);
Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true, false))
.build();
final RequestConfig params = RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MILLI)
.setSocketTimeout(TIMEOUT_MILLI).build();
CloseableHttpClient httpclient = HttpClients
.custom()
.setDefaultAuthSchemeRegistry(authSchemeRegistry)
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(params)
.build();
return httpclient;
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>parsing</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nortem-parsing-app</artifactId>

View File

@@ -11,18 +11,18 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>parsing</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-common</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-parsing</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.adrianwalker</groupId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>parsing</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nortem-parsing-storm</artifactId>

View File

@@ -9,13 +9,13 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>parsing</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-parsing-app</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>parsing</artifactId>
<groupId>uk.co.gresearch.nortem</groupId>
<version>1.05-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nortem-parsing</artifactId>

View File

@@ -11,13 +11,13 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>parsing</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem-common</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem</artifactId>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
</parent>
<modules>
<module>nortem-parsing</module>

View File

@@ -7,7 +7,7 @@
<groupId>uk.co.gresearch.nortem</groupId>
<artifactId>nortem</artifactId>
<name>nortem</name>
<version>1.06-SNAPSHOT</version>
<version>1.07-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -26,6 +26,7 @@
<module>nortem-common</module>
<module>parsing</module>
<module>alerting</module>
<module>config-editor</module>
</modules>
<dependencies>
</dependencies>