Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2020, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.modelconfig.validator;
import static com.yahoo.elide.core.dictionary.EntityDictionary.NO_VERSION;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import com.yahoo.elide.annotation.Include;
import com.yahoo.elide.annotation.SecurityCheck;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.dictionary.EntityDictionaryBuilderCustomizer;
import com.yahoo.elide.core.dictionary.EntityPermissions;
import com.yahoo.elide.core.exceptions.BadRequestException;
import com.yahoo.elide.core.security.checks.Check;
import com.yahoo.elide.core.security.checks.FilterExpressionCheck;
import com.yahoo.elide.core.security.checks.UserCheck;
import com.yahoo.elide.core.type.Type;
import com.yahoo.elide.core.utils.ClassScanner;
import com.yahoo.elide.core.utils.DefaultClassScanner;
import com.yahoo.elide.modelconfig.Config;
import com.yahoo.elide.modelconfig.DynamicConfigHelpers;
import com.yahoo.elide.modelconfig.DynamicConfigSchemaValidator;
import com.yahoo.elide.modelconfig.DynamicConfiguration;
import com.yahoo.elide.modelconfig.io.FileLoader;
import com.yahoo.elide.modelconfig.model.Argument;
import com.yahoo.elide.modelconfig.model.DBConfig;
import com.yahoo.elide.modelconfig.model.Dimension;
import com.yahoo.elide.modelconfig.model.ElideDBConfig;
import com.yahoo.elide.modelconfig.model.ElideNamespaceConfig;
import com.yahoo.elide.modelconfig.model.ElideSQLDBConfig;
import com.yahoo.elide.modelconfig.model.ElideSecurityConfig;
import com.yahoo.elide.modelconfig.model.ElideTableConfig;
import com.yahoo.elide.modelconfig.model.Join;
import com.yahoo.elide.modelconfig.model.Measure;
import com.yahoo.elide.modelconfig.model.Named;
import com.yahoo.elide.modelconfig.model.NamespaceConfig;
import com.yahoo.elide.modelconfig.model.Table;
import com.yahoo.elide.modelconfig.model.TableSource;
import com.yahoo.elide.modelconfig.store.models.ConfigFile;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import lombok.Getter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Util class to validate and parse the config files. Optionally compiles config files.
*/
public class DynamicConfigValidator implements DynamicConfiguration, Validator {
private static final Set SQL_DISALLOWED_WORDS = new HashSet<>(
Arrays.asList("DROP", "TRUNCATE", "DELETE", "INSERT", "UPDATE", "ALTER", "COMMENT", "CREATE", "DESCRIBE",
"SHOW", "USE", "GRANT", "REVOKE", "CONNECT", "LOCK", "EXPLAIN", "CALL", "MERGE", "RENAME"));
private static final String SQL_SPLIT_REGEX = "\\s+";
private static final String SEMI_COLON = ";";
private static final Pattern HANDLEBAR_REGEX = Pattern.compile("<%(.*?)%>");
@Getter private final ElideTableConfig elideTableConfig = new ElideTableConfig();
@Getter private ElideSecurityConfig elideSecurityConfig;
@Getter private Map modelVariables;
private Map dbVariables;
@Getter private final ElideDBConfig elideSQLDBConfig = new ElideSQLDBConfig();
@Getter private final ElideNamespaceConfig elideNamespaceConfig = new ElideNamespaceConfig();
private final DynamicConfigSchemaValidator schemaValidator = new DynamicConfigSchemaValidator();
private final EntityDictionary dictionary;
private final FileLoader fileLoader;
private static final Pattern FILTER_VARIABLE_PATTERN = Pattern.compile(".*?\\{\\{(\\w+)\\}\\}");
public DynamicConfigValidator(ClassScanner scanner, String configDir) {
this(builder -> builder.scanner(scanner), configDir);
}
public DynamicConfigValidator(EntityDictionaryBuilderCustomizer entityDictionaryBuilderCustomizer,
String configDir) {
this(buildEntityDictionary(entityDictionaryBuilderCustomizer), configDir);
}
public DynamicConfigValidator(EntityDictionary dictionary, String configDir) {
this.dictionary = dictionary;
this.fileLoader = new FileLoader(configDir);
initialize();
}
protected static EntityDictionary buildEntityDictionary(
EntityDictionaryBuilderCustomizer entityDictionaryBuilderCustomizer) {
EntityDictionary.EntityDictionaryBuilder builder = EntityDictionary.builder();
if (entityDictionaryBuilderCustomizer != null) {
entityDictionaryBuilderCustomizer.customize(builder);
}
return builder.build();
}
private void initialize() {
Set> annotatedClasses =
dictionary.getScanner().getAnnotatedClasses(Arrays.asList(Include.class, SecurityCheck.class));
annotatedClasses.forEach(cls -> {
if (cls.getAnnotation(Include.class) != null) {
dictionary.bindEntity(cls);
} else {
dictionary.addSecurityCheck(cls);
}
});
}
public static void main(String[] args) {
Options options = prepareOptions();
try {
CommandLine cli = new DefaultParser().parse(options, args);
if (cli.hasOption("help")) {
printHelp(options);
System.exit(0);
}
if (!cli.hasOption("configDir")) {
printHelp(options);
System.err.println("Missing required option");
System.exit(1);
}
String configDir = cli.getOptionValue("configDir");
DynamicConfigValidator dynamicConfigValidator =
new DynamicConfigValidator(new DefaultClassScanner(), configDir);
dynamicConfigValidator.readAndValidateConfigs();
System.out.println("Configs Validation Passed!");
System.exit(0);
} catch (Exception e) {
String msg = isBlank(e.getMessage()) ? "Process Failed!" : e.getMessage();
System.err.println(msg);
System.exit(2);
}
}
@Override
public void validate(Map resourceMap) {
resourceMap.forEach((path, file) -> {
if (file.getContent() == null || file.getContent().isEmpty()) {
throw new BadRequestException(String.format("Null or empty file content for %s", file.getPath()));
}
//Validate that all the files are ones we know about and are safe to manipulate...
if (file.getType().equals(ConfigFile.ConfigFileType.UNKNOWN)) {
throw new BadRequestException(String.format("Unrecognized File: %s", file.getPath()));
}
if (path.contains("..")) {
throw new BadRequestException(String.format("Parent directory traversal not allowed: %s",
file.getPath()));
}
//Validate that the file types and file paths match...
if (! file.getType().equals(FileLoader.toType(path))) {
throw new BadRequestException(String.format("File type %s does not match file path: %s",
file.getType(), file.getPath()));
}
});
readConfigs(resourceMap);
validateConfigs();
}
/**
* Read and validate config files under config directory.
* @throws IOException IOException
*/
public void readAndValidateConfigs() throws IOException {
Map loadedFiles = fileLoader.loadResources();
validate(loadedFiles);
}
public void readConfigs() throws IOException {
readConfigs(fileLoader.loadResources());
}
public void readConfigs(Map resourceMap) {
this.modelVariables = readVariableConfig(Config.MODELVARIABLE, resourceMap);
this.elideSecurityConfig = readSecurityConfig(resourceMap);
this.dbVariables = readVariableConfig(Config.DBVARIABLE, resourceMap);
this.elideSQLDBConfig.setDbconfigs(readDbConfig(resourceMap));
this.elideTableConfig.setTables(readTableConfig(resourceMap));
this.elideNamespaceConfig.setNamespaceconfigs(readNamespaceConfig(resourceMap));
populateInheritance(this.elideTableConfig);
}
public void validateConfigs() {
validateSecurityConfig();
boolean configurationExists = validateRequiredConfigsProvided();
if (configurationExists) {
validateNameUniqueness(this.elideSQLDBConfig.getDbconfigs(),
"Multiple DB configs found with the same name: ");
validateNameUniqueness(this.elideTableConfig.getTables(),
"Multiple Table configs found with the same name: ");
validateTableConfig();
validateNameUniqueness(this.elideNamespaceConfig.getNamespaceconfigs(),
"Multiple Namespace configs found with the same name: ");
validateNamespaceConfig();
validateJoinedTablesDBConnectionName(this.elideTableConfig);
}
}
@Override
public Set
getTables() {
return elideTableConfig.getTables();
}
@Override
public Set getRoles() {
return elideSecurityConfig.getRoles();
}
@Override
public Set getDatabaseConfigurations() {
return elideSQLDBConfig.getDbconfigs();
}
@Override
public Set getNamespaceConfigurations() {
return elideNamespaceConfig.getNamespaceconfigs();
}
private static void validateInheritance(ElideTableConfig tables) {
tables.getTables().stream().forEach(table -> validateInheritance(tables, table, new HashSet<>()));
}
private static void validateInheritance(ElideTableConfig tables, Table table, Set
visited) {
visited.add(table);
if (!table.hasParent()) {
return;
}
Table parent = table.getParent(tables);
if (parent == null) {
throw new IllegalStateException(
"Undefined model: " + table.getExtend() + " is used as a Parent(extend) for another model.");
}
if (visited.contains(parent)) {
throw new IllegalStateException(
String.format("Inheriting from table '%s' creates an illegal cyclic dependency.",
parent.getName()));
}
validateInheritance(tables, parent, visited);
}
private void populateInheritance(ElideTableConfig elideTableConfig) {
//ensures validation is run before populate always.
validateInheritance(this.elideTableConfig);
Set
processed = new HashSet<>();
elideTableConfig.getTables().stream().forEach(table -> populateInheritance(table, processed));
}
private void populateInheritance(Table table, Set