Initial commit

This commit is contained in:
Isaac Parenteau committed 2018-07-07 20:43:51 -05:00
commit 880e39de2e
511 files changed
+38363

No files matched your search

@@ -0,0 +1,252 @@
package net.locusworks.portal.common;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import net.locusworks.common.logger.ApplicationLogger;
import net.locusworks.common.logger.ApplicationLoggerFactory;
import net.locusworks.portal.common.services.ConfigurationService;
/***
* Defines the Portals's database connections
* @author Isaac Parenteau
*
*/
@Primary
@Component
public class PortalDataSource {
private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(PortalDataSource.class);
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String JNDI_STRING = "jdbc:mysql://%s:%d%s";
private static Map<String, String> mysqlProperties;
static {
mysqlProperties = new LinkedHashMap<>();
mysqlProperties.put("rewriteBatchedStatements", "true");
mysqlProperties.put("zeroDateTimeBehavior", "CONVERT_TO_NULL");
mysqlProperties.put("useSSL", "false");
mysqlProperties.put("serverTimezone", TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT));
}
private Map<Jndi, DataSource> dataSources = new HashMap<>();
protected static enum Jndi {
PORTAL_USER,
PORTAL_ROOT
}
@Autowired
private ConfigurationService configurationService;
/**
* Default constructor
*/
public PortalDataSource() {
dataSources = new HashMap<>();
}
/**
* Create the data source bean
* @return the data source bean
*/
@Bean
public DataSource dataSource() {
return getPortalDataSource();
}
/***
* Creates a database connection
* This will create a connection and store it in the data pool
* which will allow reuse of connections instead of creating new connections every time
* @param url URL to the database
* @param userName The username for the database
* @param password the password for the database
* @return A Datasource representing the connection
* @throws Exception
*/
private static DataSource createDataSource(String url, String userName, String password) throws Exception {
Class.forName(DRIVER).newInstance();
Properties props = new Properties();
props.setProperty("user", userName);
props.setProperty("password", password);
List<String> params = mysqlProperties
.entrySet()
.stream()
.map(prop -> String.format("%s=%s", prop.getKey(), prop.getValue()))
.collect(Collectors.toList());
url = String.format("%s?%s", url, StringUtils.join(params, "&"));
//Create a connection factory that the pool will use to create connections
ConnectionFactory cf = new DriverManagerConnectionFactory(url, props);
//Create the poolable connection factory
PoolableConnectionFactory pcf = new PoolableConnectionFactory(cf, null);
pcf.setValidationQuery("SELECT 1");
pcf.setDefaultAutoCommit(true);
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMinIdle(10);
poolConfig.setMaxTotal(100);
AbandonedConfig abandonConfig = new AbandonedConfig();
abandonConfig.setRemoveAbandonedTimeout(60);
abandonConfig.setLogAbandoned(false);
abandonConfig.setRemoveAbandonedOnBorrow(true);
abandonConfig.setRemoveAbandonedOnMaintenance(true);
//Create the pool of connections
GenericObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(pcf, poolConfig);
connectionPool.setTestOnBorrow(true);
connectionPool.setTestWhileIdle(true);
connectionPool.setTimeBetweenEvictionRunsMillis(10000);
connectionPool.setMinEvictableIdleTimeMillis(1000);
connectionPool.setAbandonedConfig(abandonConfig);
pcf.setPool(connectionPool);
//Pooling data source itself
PoolingDataSource<PoolableConnection> dataSource = new PoolingDataSource<>(connectionPool);
return dataSource;
}
/**
* Creates a datasouce from the JNDI type
* @param jndi Root or User JNDI
* @return
* @throws Exception
*/
private DataSource createDataSource(Jndi jndi) throws Exception {
if (dataSources.containsKey(jndi)) {
return dataSources.get(jndi);
}
String tail = null;
boolean isRoot = false;
switch (jndi) {
case PORTAL_ROOT:
tail = "";
isRoot = true;
break;
case PORTAL_USER:
tail = "/portal";
break;
default:
throw new Exception("Unknown data source specifier: " + jndi);
}
String userName = isRoot ? configurationService.getDatabaseRootUsername() : configurationService.getDatabaseUsername();
String passwd = isRoot? configurationService.getDatabaseRootPassword() : configurationService.getDatabasePassword();
String url = String.format(JNDI_STRING, configurationService.getDatabaseHost(), configurationService.getDatabasePort(), tail);
dataSources.put(jndi, createDataSource(url, userName, passwd));
return dataSources.get(jndi);
}
/**
* Get the datasource
* @param jndiName name of the data source to retrieve
* @return
*/
private DataSource getDataSource(Jndi jndiName) {
// locate the DataSource
DataSource ds = null;
try {
ds = dataSources.get(jndiName);
if (ds == null) {
logger.info("JNDI %s not found. Creating it", jndiName.toString());
// Get the data source from initial context or by building our own pooling data source
ds = createDataSource(jndiName);
dataSources.put(jndiName, ds);
} else {
logger.info("Loading JNDI %s", jndiName.toString());
}
} catch (Exception e) {
logger.error("Unable to get datasource", e);
}
return ds;
}
/**
* Get the normal data source that is used for the portal application
* @return the user data source
*/
public DataSource getPortalDataSource() {
return getDataSource(PortalDataSource.Jndi.PORTAL_USER);
}
/**
* Get the database root data source.
* This data source is only used to do the migrations then closed
* @return the root data source
*/
public DataSource getPortalRootDataSource() {
return getDataSource(PortalDataSource.Jndi.PORTAL_ROOT);
}
/**
* Close the root data source once it is no longer necessary.
*/
public void closeRootDataSource() {
DataSource ds = dataSources.get(PortalDataSource.Jndi.PORTAL_ROOT);
try {
if (ds != null) {
if (ds instanceof PoolingDataSource<?>) {
((PoolingDataSource<?>)ds).close();
} else {
ds.getConnection().close();
}
dataSources.remove(PortalDataSource.Jndi.PORTAL_ROOT);
}
} catch (Exception e) { }
}
/**
* Close the data source connections when the server shots down
*/
public void closeDataSources() {
for (Iterator<Map.Entry<Jndi, DataSource>> it = dataSources.entrySet().iterator(); it.hasNext();) {
Map.Entry<Jndi, DataSource> ds = it.next();
try {
logger.info("Closing datasource " + ds.getKey());
DataSource dataSource = ds.getValue();
if (dataSource instanceof PoolingDataSource<?>) {
((PoolingDataSource<?>)dataSource).close();
} else {
dataSource.getConnection().close();
}
} catch (Exception ex) {
logger.error("Unable to close datasource %s -> %s", ds.getKey(), ex.getMessage());
} finally {
it.remove();
}
}
}
}
@@ -0,0 +1,108 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration specifying the allowed configuration values
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public enum Configuration {
DB_ROOT_USER("dbRootUser"),
DB_ROOT_PASSWORD("dbRootPassword"),
DB_USER("dbUsername"),
DB_PASSWORD("dbPassword"),
DB_HOST("dbHost"),
DB_PORT("dbPort"),
USER_EXPIRATION_DAYS("userExpirationDays"),
CA_CERT_FILE("caCertFile"),
CA_CERT_PASSWORD("caCertPassword"),
AES_SEED_FILE("aesSeedFile"),
LOG_LEVEL("logLevel");
private String value;
private Configuration(String value) {
this.value = value;
}
/**
* Get the current value of the enumeration
* @return value
*/
public String getValue() {
return this.value;
}
@Override
public String toString() {
return this.value;
}
/**
* Checks to see if the configuration is of a certain value
* @param confTypes types to check against
* @return true if the current configuration is of a certain enumeration, false otherwise
*/
public boolean is(Configuration... confTypes) {
for (Configuration confType : confTypes) {
if (this == confType) {
return true;
}
}
return false;
}
/**
* Checks to see if the configuration is not of a certain value
* @param confTypes Types to check against
* @return true if the current configuration is not of a certain enuermation, false otherwise
*/
public boolean isNot(Configuration... confTypes) {
return !is(confTypes);
}
/**
* Finds a specific enumeration
* @param value Value to find
* @return the found enumeration or null if nothing is found
*/
public static Configuration findEnum(String value) {
for (Configuration conf: values()) {
if (conf.getValue().equalsIgnoreCase(value)) {
return conf;
}
}
return null;
}
/**
* Checks to see if a specific value is in a set of configuration types
* @param value Value to look for
* @param confTypes types to compare against
* @return true if the string value is in a certain set of configuration enumerations, false otherwise
*/
public static boolean in(String value, Configuration... confTypes) {
Configuration ft = findEnum(value);
if (ft == null) {
return false;
}
for (Configuration confType : confTypes) {
if (ft == confType) {
return true;
}
}
return false;
}
/**
* Checks to see if a specific value is not in a set of configuration types
* @param value Value to look for
* @param confTypes types to compare against
* @return true if the string value is not in a certain set of configuration enumerations, false otherwise
*/
public static boolean notIn(String value, Configuration... confTypes) {
return !in(value, confTypes);
}
}
@@ -0,0 +1,71 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration specifying the allowed credential types
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public enum CredentialType {
SSH,
PASSWD;
/**
* Checks to see if the credential type is of a certain value
* @param credentialTypes types to check against
* @return true if the current credential type is of a certain enumeration, false otherwise
*/
public boolean is(CredentialType... credentialTypes) {
for (CredentialType credType : credentialTypes) {
if (this == credType) {
return true;
}
}
return false;
}
/**
* Get the credential type based off the string value
* @param value value to check
* @return the credential type if one is found or null
*/
public static CredentialType getEnum(String value) {
for (CredentialType type : values()) {
if (type.toString().equalsIgnoreCase(value)) {
return type;
}
}
return null;
}
/**
* Checks to see if a specific value is in a set of credential types
* @param value Value to look for
* @param credentialTypes types to compare against
* @return true if the string value is in a certain set of credential types enumerations, false otherwise
*/
public static boolean in(String value, CredentialType... credentialTypes) {
CredentialType ft = getEnum(value);
if (ft == null) {
return false;
}
for (CredentialType freqType : credentialTypes) {
if (ft == freqType) {
return true;
}
}
return false;
}
/**
* Checks to see if a specific value is not in a set of credential types
* @param value Value to look for
* @param credentialTypes types to compare against
* @return true if the string value is not in a certain set of credential types enumerations, false otherwise
*/
public static boolean notIn(String value, CredentialType... credentialTypes) {
return !in(value, credentialTypes);
}
}
@@ -0,0 +1,65 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration specifying the different types of allowed events
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public enum EventType {
LOGIN,
LOGOUT,
USER_ADDED,
USER_EDITED,
USER_DELETED,
CREDENTIALS_ADDED,
CREDENTIALS_REMOVED,
REPOSITORY_ADDED,
REPOSITORY_PULL,
REPOSITORY_DELETED,
SCRIPT_ADDED,
SCRIPT_EDITED,
SCRIPT_DELETED,
SCRIPT_SCHEDULE_DELETED,
ENVVAR_ADDED,
ENVVAR_EDITED,
ENVVAR_DELETED,
SCRIPT_RUN,
SCRIPT_END,
SCRIPT_ERROR,
SSH_RUN,
SSH_END,
SSH_ERROR,
SCP_RUN,
SCP_END,
SCP_ERROR,
LOGS_PURGED,
HASH_TYPE_UPDATED,
CONFIGURATION_UPDATED,
PERMISSIONS_CHANGED,
PERMISSIONS_DELETED,
DIRECTORY_PERMISSIONS_CHANGED,
DIRECTORY_PERMISSIONS_REMOVED,
FILE_DOWNLOADED,
FILE_DELETED,
EMAIL_TEMPLATED_ADDED,
EMAIL_TEMPLATED_EDITED,
EMAIL_TEMPLATED_DELETED,
SMTP_CONFIG_CHANGED,
MESSAGE,
EXCEPTION;
public String getValue() {
//Returns the enum type with the underscores replaced with spaces
return this.toString().replace("_", " ").trim().toUpperCase();
}
public static EventType getEnum(String value) {
for (EventType type : values()) {
if (type.getValue().equalsIgnoreCase(value)) {
return type;
}
}
throw new IllegalArgumentException("No EventType found for value of: " + value);
}
}
@@ -0,0 +1,63 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration containing the frequency types allowed for scheduling events
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public enum FrequencyType {
NOW,
HOURLY,
DAILY,
WEEKLY,
MONTHLY,
YEARLY,
SPECIFIC,
TRIGGERED;
/**
* Get a frequency type by string value
* @param value frequency type to check for
* @return frequencyType if found; null otherwise
*/
public static FrequencyType getEnum(String value) {
for (FrequencyType type : values()) {
if (type.toString().equalsIgnoreCase(value)) {
return type;
}
}
return null;
}
/**
* checks to see if a string value is in a certain set of frequency types
* @param value Value to check
* @param frequencyTypes frequency types to check against
* @return true if found within the set, false otherwise
*/
public static boolean in(String value, FrequencyType... frequencyTypes) {
FrequencyType ft = getEnum(value);
if (ft == null) {
return false;
}
for (FrequencyType freqType : frequencyTypes) {
if (ft == freqType) {
return true;
}
}
return false;
}
/**
* checks to see if a string value is not in a certain set of frequency types
* @param value Value to check
* @param frequencyTypes frequency types to check against
* @return false if found within the set, true otherwise
*/
public static boolean notIn(String value, FrequencyType... frequencyTypes) {
return !in(value, frequencyTypes);
}
}
@@ -0,0 +1,49 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration specifying the allowed permissions
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*
*/
public enum PermissionType {
VIEW_SCRIPT_TAB,
VIEW_GIT_TAB,
VIEW_USER_TAB,
VIEW_CREDS_TAB,
VIEW_LOG_TAB,
VIEW_PERMISSION_TAB,
VIEW_EMAIL_TAB,
VIEW_ENV_VARS_TAB,
VIEW_CONFIG,
CHANGE_CONFIG,
VIEW_SMTP_CONFIG,
EDIT_SMTP_CONFIG,
VIEW_HASH_TYPES,
CHANGE_HASH_TYPES,
ADD_USERS,
DELETE_USERS,
PURGE_LOGS,
ADD_PERMS,
DELETE_PERMS,
ADD_CREDENTIALS,
DELETE_CREDENTIALS,
ADD_GIT_REPO,
PULL_GIT_REPO,
DELETE_GIT_REPO,
ADD_EMAIL,
DELETE_EMAIL,
RUN_SCRIPT,
ADD_SCRIPT,
DELETE_SCRIPT,
VIEW_SCRIPT_SCHED_STATUS,
DELETE_SCRIPT_SCHED_STATUS,
ADD_ENV_VARS,
DELETE_ENV_VARS,
VIEW_ENV_VARS,
VIEW_LOG_LEVELS,
CHANGE_LOG_LEVELS,
DELETE_FILE,
UPDATE_SEED_VALUE
}
@@ -0,0 +1,28 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration for the allowed script types when creating a task
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public enum ScriptType {
LOCAL,
REMOTE,
SCP,
NULL;
/**
* Get a script type based off from string value
* @param value value to check
* @return script type if the value is found. False otherwise
*/
public static ScriptType getEnum(String value) {
for (ScriptType type : values()) {
if (type.toString().equalsIgnoreCase(value)) {
return type;
}
}
return ScriptType.NULL;
}
}
@@ -0,0 +1,32 @@
package net.locusworks.portal.common.enums;
/**
* Enumeration specifying the allowed trigger types
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*
*/
public enum TriggerType {
APP_LOGS_UPDATED,
APP_LOGS_PURGED,
SCRIPT_COMPLETED,
SCRIPT_DELETED,
SCRIPT_ADDED,
SCRIPT_UPDATED,
SCRIPT_SCHEDULE_DELETED,
USER_ADDED,
USER_DELETED,
HASH_TYPES_CHANGED,
PERMISSIONS_DELETED,
PERMISSIONS_CHANGED,
PERMISSIONS_UPDATED,
PERMISSIONS_ADDED,
FILE_DELETED,
ENV_VARS_ADDED,
ENV_VARS_DELETED,
EMAIL_TEMPLATE_UPDATED,
EMAIL_TEMPLATE_DELETED,
SMTP_CONFIG_UPDATED,
SEED_UPDATED
}
@@ -0,0 +1,49 @@
package net.locusworks.portal.common.exceptions;
import net.locusworks.common.exceptions.ApplicationException;
/***
* Custom exception class for the portal program
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public class PortalException extends ApplicationException {
private static final long serialVersionUID = 1L;
/**
* Exception is thrown when there is no file found with a given file name
* @param fileName The file name of the file trying to be located
* @return no such file error message
*/
public static PortalException noSuchFile(String fileName) {
return new PortalException(9010, String.format("No such file found for %s", fileName));
}
/**
* Exception thrown when a file is unable to be deleted
* @param filename Name of the file that couldn't be deleted
* @return unable to delete file error message
*/
public static PortalException unableToDeleteFile(String filename) {
return new PortalException(9010, String.format("Unable to delete file %s. Please check permissions", filename));
}
/**
* Constructor for the PortalException
* @param code error code
* @param message error message
*/
private PortalException(int code, Exception ex) {
super(code, ex);
}
/**
* Constructor for the PortalException
* @param code error code
* @param message error message
*/
private PortalException(int code, String message) {
super(code, message);
}
}
@@ -0,0 +1,131 @@
package net.locusworks.portal.common.http;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import net.locusworks.common.logger.ApplicationLogger;
import net.locusworks.common.logger.ApplicationLoggerFactory;
/**
* Class to handle http(s) connections
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public class HttpClientHelper {
private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(HttpClientHelper.class);
public enum HttpSchema {
HTTP,
HTTPS;
public static HttpSchema findEnum(String value) {
for (HttpSchema schema : values()) {
if (value.equalsIgnoreCase(schema.toString())) {
return schema;
}
}
return null;
}
}
private static final String[] TLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"};
private HttpClient client;
private String baseUrl;
/**
* Constructor to handle http connection
* @param protocol protocol to use (http or https)
* @param host the host url
* @param port the host port
* @throws Exception exception
*/
public HttpClientHelper(String protocol, String host, String port) throws Exception {
HttpSchema schema = HttpSchema.findEnum(protocol);
if (schema == null) {
throw new Exception("Unable to find http schema of " + protocol);
}
this.baseUrl = String.format("%s://%s:%s", schema.toString().toLowerCase(), host, port);
this.client = createClient(schema);
}
/**
* Create a http client
* @param schema Schema to connect via (HTTP or HTTPS)
* @return newly created http client
* @throws Exception thrown when the client can't be created
*/
private HttpClient createClient(HttpSchema schema) throws Exception {
HttpClientBuilder builder = HttpClientBuilder.create();
if (schema == HttpSchema.HTTP) {
return builder.build();
}
//Trust all ssl certificates
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() { return null; }
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
}
};
//Setup the ssl instance using tls
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, TLS, null, new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) { return true; }
});
builder = builder.setSSLSocketFactory(sslsf);
return builder.build();
}
/**
* Get the http GET response code
* @param endpoint endpoint to get the response from
* @return responseCode
* @throws Exception general exception
*/
public Integer getGetResponseCode(String endpoint) throws Exception {
String url = this.baseUrl + endpoint;
HttpResponse response = this.client.execute(new HttpGet(url));
HttpEntity entity = response.getEntity();
String responseString = EntityUtils.toString(entity);
logger.info("Response from stomp: " + responseString);
Integer responseCode = response.getStatusLine().getStatusCode();
EntityUtils.consume(entity);
return responseCode;
};
}
@@ -0,0 +1,181 @@
package net.locusworks.portal.common.services;
import javax.annotation.PostConstruct;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import net.locusworks.common.crypto.AES;
import net.locusworks.common.utils.Utils;
import net.locusworks.portal.common.triggers.SeedTrigger;
import static net.locusworks.common.Charsets.UTF_8;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
/**
* Service to handle AES encryption
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
@Service
public class AESService implements ApplicationListener<SeedTrigger>{
private static final String PORTAL_AES_SEED = "PORTAL_AES_SEED";
private static final String DEFAULT_SEED = "portal.tomcat";
private String seedLoc;
private AES aes;
@Autowired
private ConfigurationService confService;
/**
* Initialize the aes engine by loading the seed file.
* This file is needed to decrypt the aes.
*/
@PostConstruct
public void init() {
try {
confService.init(); //initialize our conf file to pick up the latest changes if any
//Check to see if the environment variable is set first
seedLoc = System.getProperty(PORTAL_AES_SEED) == null ? System.getenv(PORTAL_AES_SEED) : System.getProperty(PORTAL_AES_SEED);
if (seedLoc == null) {
//If it can't find it in the environment variable load it from the properties file
seedLoc = confService.getAesSeedFile();
}
if (seedLoc == null) {
throw new RuntimeException("Seed location is not set... unable to continue");
}
File seedFile = new File(seedLoc);
if (!seedFile.exists()) {
try(BufferedReader br = getReader()) {
StringBuilder seeder = new StringBuilder();
while(br.ready()) {
seeder.append(br.readLine());
}
saveSeed(seeder.toString());
}
seedFile = new File(seedLoc);
if (!seedFile.canRead()) {
throw new IOException("Unable to read file");
}
}
String seed = IOUtils.toString(new FileInputStream(seedFile), UTF_8).trim();
aes = AES.createInstance(seed);
} catch (Exception e) {
throw new RuntimeException("Unable to read file", e);
}
}
private BufferedReader getReader() throws IOException {
InputStream is = this.getClass().getResourceAsStream(DEFAULT_SEED);
if (is == null) {
is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_SEED);
}
if (is == null) {
throw new IOException("Unable to read default seed file from resources");
}
return new BufferedReader(new InputStreamReader(is, UTF_8));
}
/**
* Encrypt a string
* @param plainText string to encrypt
* @return encrypted string
*/
public String encrypt(String plainText) {
return aes.encrypt(plainText);
}
/**
* Encrypt a byte array
* @param plainText the byte array to encrypt
* @return encrypted string
*/
public byte[] encrypt(byte[] plainText) {
return encrypt(new String(plainText, UTF_8)).getBytes(UTF_8);
}
/**
* Decrypt an ecnrypted byte array
* @param cypherBytes byte array to decrypt
* @return decrypted string
*/
public String decrypt(byte[] cypherBytes) {
return decrypt(new String(cypherBytes, UTF_8));
}
/**
* Decrypt an encrypted string
* @param cypherText the encrypted string to decrypt
* @return decrypted string
*/
public String decrypt(String cypherText) {
return aes.decrypt(cypherText);
}
/**
* Change the encryption value
* @param oldEncryptedValue the old encryption value
* @param newSeed the new seed to set the value too
* @return new encryption value
*/
public String changeEncryptionValue(String oldEncryptedValue, String newSeed) {
String oldValue = decrypt(oldEncryptedValue);
String newValue = AES.createInstance(newSeed).encrypt(oldValue);
return newValue;
}
public String changeEncryptionValue(byte[] oldValue, String newSeed) {
return changeEncryptionValue(new String(oldValue, UTF_8), newSeed);
}
/**
* Change the seed file with the new seed. <br/>
* <b>Warning:</b> once the seed has changed all encrypted values that
* were encrypted with the previous seed will no longer decrypt.
* Make sure to write a service that will decrypt all the previous values
* then encrypt with new seed
* @param newSeed the new seed to use
* @throws IOException occurs when the seed can't be written
*/
public void saveSeed(String newSeed) throws IOException {
File seedFile = new File(seedLoc);
if (seedFile.exists() && (!seedFile.isFile() || !seedFile.canRead())) {
throw new IOException("Unable to read or locate file");
}
try(FileWriter fw = new FileWriter(seedFile, false)) {
fw.write(newSeed);
fw.flush();
}
try {
//Set the file permission will throw UnsupportedOperationException if on windows machine
//Can ignore the exception
Set<PosixFilePermission> perms = Utils.toSet(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);
Files.setPosixFilePermissions(seedFile.toPath(), perms);
} catch (UnsupportedOperationException ex) {}
}
@Override
public void onApplicationEvent(SeedTrigger event) {
String seed = event.getSeed();
this.aes.setSeed(seed);
}
}
@@ -0,0 +1,420 @@
package net.locusworks.portal.common.services;
import static net.locusworks.portal.common.utils.Constants.PROPERTIES_FILE;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import net.locusworks.common.configuration.PropertiesManager;
import net.locusworks.common.logger.ApplicationLogger;
import net.locusworks.common.logger.ApplicationLoggerFactory;
import net.locusworks.portal.common.enums.Configuration;
import net.locusworks.portal.common.exceptions.PortalException;
import net.locusworks.portal.json.ConfigurationRequest;
/**
* Configuration Service class that reads the conf file
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
@Service
public final class ConfigurationService {
private Properties configuration;
private Properties defaults = null;
private File portalConf = null;
private String keyDir = null;
private long lastModified = 0;
private static final String DB_ROOT_USER_PROPERTY = "DB_ROOT_USER";
private static final String DB_ROOT_PASSWD_ENV = "DB_ROOT_PASSWD";
private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(ConfigurationService.class);
@Autowired
private AESService aesService;
/**
* Initialize the configuration service
* This happens during server start up
* @throws IOException If the file cannot be read
* @throws Exception any other exception thrown
*/
@PostConstruct
public void init() throws IOException, Exception {
// load defaults when the webapp loads
try {
defaults = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE);
} catch (IOException ex) {
logger.error(String.format("Failed to load default %s: %s", PROPERTIES_FILE, ex.getMessage()));
throw ex;
}
// create portalConf File object
portalConf = new File(String.format("%s/conf/%s", System.getProperty("catalina.base"), PROPERTIES_FILE));
//Create a temporary key directory
keyDir = String.format("%s/keys", System.getProperty("catalina.base"));
File keyDirFile = new File(keyDir);
if (!keyDirFile.exists()) {
try {
keyDirFile.mkdirs();
} catch (Exception ex) {
throw ex;
}
}
// initial config load
loadConf();
}
/**
* Load the configuration file
*/
private void loadConf() {
// load the active config file
// ignore read error, we can continue with an empty configuration map
// and all default items will be added below and the file created
logger.info("Loading config file: " + portalConf);
try {
configuration = PropertiesManager.loadConfiguration(portalConf);
} catch (Exception e) {
logger.info("Config file: " + portalConf + " will be created from template");
configuration = new Properties();
}
Map<String, String> results = PropertiesManager.addConfiguration(configuration, defaults);
boolean changed = !results.isEmpty();
if (!results.isEmpty()) {
logger.info(results, new StringBuilder("Added new configuration items:%n"));
}
results = PropertiesManager.removeConfiguration(configuration, defaults);
changed |= !results.isEmpty();
if (!results.isEmpty()) {
logger.info(results, new StringBuilder("Removed unused configuration items:%n"));
}
if (changed) {
PropertiesManager.saveConfiguration(configuration, portalConf, "Patch Repository properties file");
}
lastModified = portalConf.lastModified();
}
/**
* Save the configuration values to file
* @param confs Configuration property values to save
* @throws Exception general exception
*/
public void saveToConf(Properties confs) throws Exception {
PropertiesManager.saveConfiguration(confs, portalConf, "Patch Repository properties file");
logger.info("Saved config file: " + portalConf + ", " + confs.size() + " entries");
loadConf();
}
/**
* Get the database root user name.
* It first checks the catalina.properties file for the root user
* then checks the patchrepo.properties file
* @return root database username
*/
public String getDatabaseRootUsername() {
String rootUser = System.getProperty(DB_ROOT_USER_PROPERTY);
return rootUser != null ? rootUser : configuration.getProperty(Configuration.DB_ROOT_USER.getValue());
}
/**
* Get the database root user password
* It first checks the catalina.properties file for the password
* then checks the patchrepo.properties file.
* The password must be encrypted in both locations
* @return root database password
*/
public String getDatabaseRootPassword() {
String rootPassword = System.getProperty(DB_ROOT_PASSWD_ENV);
String dbPasswd = rootPassword == null ? configuration.getProperty(Configuration.DB_ROOT_PASSWORD.getValue()) : rootPassword;
try {
return aesService.decrypt(dbPasswd);
} catch (Exception ex) {
logger.error("Unable to get db root password " + ex.getMessage());
return null;
}
}
/**
* Get the standard database user name
* @return portal database username
*/
public String getDatabaseUsername() {
return configuration.getProperty(Configuration.DB_USER.getValue());
}
/**
* Get the standard database password.
* The password is encrypted in the properties file
* @return patchrepo database password
*/
public String getDatabasePassword() {
String dbPasswd = configuration.getProperty(Configuration.DB_PASSWORD.getValue());
try {
return aesService.decrypt(dbPasswd);
} catch (Exception ex) {
logger.error("Unable to get db password " + ex.getMessage());
return null;
}
}
/**
* Get the database host url
* @return database host url
*/
public String getDatabaseHost() {
return configuration.getProperty(Configuration.DB_HOST.getValue());
}
/**
* Get the database host port
* @return the database port
*/
public Integer getDatabasePort() {
return Integer.parseInt(configuration.getProperty(Configuration.DB_PORT.getValue()));
}
/**
* Get the default number of days until a new user expires
* @return number of days after creation the user will expire by default
*/
public Integer getUserExpirationDays() {
return Integer.parseInt(configuration.getProperty(Configuration.USER_EXPIRATION_DAYS.getValue()));
}
/**
* Directory to store temporary private keys
* @return directory location
*/
public String getKeyDirectory() {
return keyDir;
}
/**
* Get the CA cert file
* @return the ca cert file
*/
public String getCaCertFile() {
String caCertFile = configuration.getProperty(Configuration.CA_CERT_FILE.toString(), "");
caCertFile = replaceProperties(caCertFile);
return caCertFile;
}
/**
* get the ca cert password
* the password is encrypted
* @return the ca cert file password
*/
public String getCaCertPassword() {
String caCertPasswd = configuration.getProperty(Configuration.CA_CERT_PASSWORD.getValue());
try {
return aesService.decrypt(caCertPasswd);
} catch (Exception ex) {
logger.error("Unable to get db password " + ex.getMessage());
return null;
}
}
/**
* Get the aes seed file for aes encryption
* @return seed file location
*/
public String getAesSeedFile() {
String aesSeed = configuration.getProperty(Configuration.AES_SEED_FILE.getValue(), "");
aesSeed = replaceProperties(aesSeed);
return aesSeed;
}
/**
* Get configuration value
* @param key key for the value to find
* @return the configuration value
*/
public ConfigurationRequest getConfiguration(String key) {
Configuration type = Configuration.findEnum(key);
return getConfiguration(type);
}
/**
* Get configuration value
* @param typse the configuration types to get
* @return the configuration value
*/
public ConfigurationRequest getConfigurations(Configuration... types) {
ConfigurationRequest cr = new ConfigurationRequest();
for (Configuration type : types) {
String key = type.getValue();
String value = this.configuration.getProperty(key);
switch(type) {
case CA_CERT_FILE:
cr.setCaCertFile(value);
break;
case CA_CERT_PASSWORD:
cr.setCaCertPassword(value);
break;
case DB_HOST:
cr.setDbHost(value);
break;
case DB_PASSWORD:
cr.setDbPassword(value);
break;
case DB_PORT:
cr.setDbPort(Integer.parseInt(value));
break;
case DB_ROOT_PASSWORD:
cr.setDbRootPassword(value);
break;
case DB_ROOT_USER:
cr.setDbRootUser(value);
break;
case DB_USER:
cr.setDbUsername(value);
break;
case USER_EXPIRATION_DAYS:
cr.setUserExpirationDays(Integer.parseInt(value));
break;
case AES_SEED_FILE:
cr.setAesSeedFile(value);
break;
case LOG_LEVEL:
cr.setLogLevel(value);
break;
default:
break;
}
}
return cr;
}
/**
* Get configuration value
* @param type the configuration type to get
* @return the configuration value
*/
public ConfigurationRequest getConfiguration(Configuration type) {
return getConfigurations(type);
}
/**
* Get the current configuration
* @return configuration
*/
public Properties getConfiguration() {
return configuration;
}
/**
* Get the last time the configuration file was modified
* @return last modified
*/
public long getLastModified() {
return lastModified;
}
public void updateEncryption(String newSeed) throws Exception {
String newDBRootPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.DB_ROOT_PASSWORD.getValue()), newSeed);
String newDBPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.DB_PASSWORD.getValue()), newSeed);
String newCACertPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.CA_CERT_PASSWORD.getValue()), newSeed);
ConfigurationRequest cr = getConfigurations(Configuration.values());
cr.setCaCertPassword(newCACertPassword);
cr.setDbRootPassword(newDBRootPassword);
cr.setDbPassword(newDBPassword);
saveConfiguration(cr, false);
}
/**
* Save the current configuration
* @param request Request with configuration values
* @throws PortalException actionNotPermitted
*/
public void saveConfiguration(ConfigurationRequest request) throws Exception {
saveConfiguration(request, true);
}
/**
* Save the current configuration
* @param request request with the configuration values
* @param encryptCreds set to true to encrypt credentials false if the credentials are already encrypted
* @throws Exception PortalException actionNotPermitted
*/
public void saveConfiguration(ConfigurationRequest request, boolean encryptCreds) throws Exception {
try {
Properties props = new Properties();
//copy what is current in the configuration settings into the new properties file
Enumeration<?> confEnumeration = configuration.propertyNames();
while(confEnumeration.hasMoreElements()) {
String key = (String)confEnumeration.nextElement();
String value = configuration.getProperty(key).toString();
props.setProperty(key, value);
}
boolean changed = false;
for (Field f : request.getClass().getDeclaredFields()) {
f.setAccessible(true);
String fieldName = f.getName();
//Ensures we are only saving values that are already configured
Configuration conf = Configuration.findEnum(fieldName);
if (conf == null) continue;
Object value = f.get(request);
if (value == null) continue;
String fieldValue = String.valueOf(value);
//Check to see if the old value changed
String oldValue = props.getProperty(conf.getValue());
if (StringUtils.isAllBlank(oldValue, fieldValue) || oldValue.equals(fieldValue)) { continue; }
changed = true;
fieldValue = encryptCreds && conf.is(Configuration.DB_ROOT_PASSWORD, Configuration.DB_PASSWORD, Configuration.CA_CERT_PASSWORD) ? aesService.encrypt(fieldValue) : fieldValue;
props.setProperty(fieldName, fieldValue);
}
if (changed) {
saveToConf(props);
}
} catch (Exception ex) {
throw PortalException.actionNotPermitted(ex.getMessage());
}
}
/**
* Loop through the system properties and replace
* the property value with the system property
* @param value value to check
* @return fromatted string
*/
private String replaceProperties(String value) {
String tmp = new String(value);
Enumeration<?> sysPropEnum = System.getProperties().propertyNames();
while (sysPropEnum.hasMoreElements()) {
String key = (String) sysPropEnum.nextElement();
String variableKey = String.format("${%s}", key);
tmp = tmp.replace(variableKey, System.getProperty(key));
}
return tmp;
}
}
@@ -0,0 +1,49 @@
package net.locusworks.portal.common.triggers;
import java.util.ArrayList;
import java.util.List;
import net.locusworks.common.utils.Utils;
import net.locusworks.portal.common.enums.TriggerType;
/**
* Account trigger class to send stomp triggers across the system and to the client
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public class AccountTrigger extends Trigger {
private static final long serialVersionUID = 2139401946845273241L;
private Integer userId;
public AccountTrigger() {
super();
}
public AccountTrigger(TriggerType type, Object... resources) {
this();
setType(type);
setResources(Utils.toList(resources));
}
private List<Object> resources = new ArrayList<>();
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public List<Object> getResources() {
return resources;
}
public void setResources(List<Object> resources) {
this.resources = resources;
}
}
@@ -0,0 +1,34 @@
package net.locusworks.portal.common.triggers;
import org.springframework.context.ApplicationEvent;
public class SeedTrigger extends ApplicationEvent {
private static final long serialVersionUID = 8676727530092345425L;
private String seed;
public SeedTrigger() {
super(new Object());
}
public SeedTrigger(String seed) {
this();
this.seed = seed;
}
/**
* @return the seed
*/
public String getSeed() {
return seed;
}
/**
* @param seed the seed to set
*/
public void setSeed(String seed) {
this.seed = seed;
}
}
@@ -0,0 +1,63 @@
package net.locusworks.portal.common.triggers;
import org.springframework.context.ApplicationEvent;
import net.locusworks.portal.common.enums.TriggerType;
/**
* Abstract class to send stomp events across the system
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
public abstract class Trigger extends ApplicationEvent {
private static final long serialVersionUID = 3123109911284540118L;
private long timestampCreation;
private TriggerType type;
public Trigger() {
this(new Object());
}
/**
* Default constructor
* @param source trigger payload
*/
public Trigger(Object source) {
super(source);
}
/**
* get the timestamp at which the trigger was created
* @return timestampCreation
*/
public long getTimestampCreation() {
return timestampCreation;
}
/**
* Set the timestamp the trigger was created
* @param timestampCreation timestamp
*/
public void setTimestampCreation(long timestampCreation) {
this.timestampCreation = timestampCreation;
}
/**
* Get the trigger type
* @return triggerType
*/
public TriggerType getType() {
return type;
}
/**
* Set the trigger type
* @param type Trigger type to set
*/
public void setType(TriggerType type) {
this.type = type;
}
}
@@ -0,0 +1,27 @@
package net.locusworks.portal.common.utils;
import java.io.File;
import org.apache.commons.io.FilenameUtils;
/**
* Class to hold final static constant values used across the system
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
*/
public class Constants {
public static final String SEPARATOR = FilenameUtils.separatorsToUnix(File.separator);
public static final String SCHEDULER_GROUP_NAME = "scriptScheduler";
public static final String KEY_STORE_PATH = "javax.net.ssl.keyStore";
public static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword";
public static final String TRUST_STORE_PATH = "javax.net.ssl.trustStore";
public static final String TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
public static final String PROPERTIES_FILE = "portal.properties";
}
@@ -0,0 +1,199 @@
package net.locusworks.portal.common.utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import net.locusworks.portal.common.services.ConfigurationService;
/**
* Component class to help convert dates to human readable format
* @author Isaac Parenteau
* @since 1.0.0-RELEASE
* @version 1.0
*/
@Component
public class DateService {
private static final String DEFAULT = "MM/dd/yyyy";
private static final String EXPANDED = "MM/dd/yyyy HH:mm:ss z";
private static final String PRETTY_EXPANDED = "MMM d, yyyy HH:mm:ss z";
@Autowired
private ConfigurationService confService;
/**
* Get the date in which a new user would expire
* @return Returns the user expiration date
*/
public Date getUserExpirationDate() {
return createDate(Calendar.DATE, confService.getUserExpirationDays());
}
/**
* Create a new date
* @return new Date with todays date
*/
public Date createDate() {
return createDate(Calendar.DATE, 0);
}
/***
* Create a date
* @param calField Field in the calendar class to add time to
* @param numToAdd Number to add corresponding to the field can be negative
* @return new date with the offset applied
*/
public Date createDate(Integer calField, Integer numToAdd) {
Calendar cal = Calendar.getInstance();
Date date = new Date();
cal.setTime(date);
cal.add(calField, numToAdd);
return cal.getTime();
}
/**
* Format a date to the default format of "MM/dd/yyyy"
* @param date Date to format
* @return formatted String
*/
public static String defaultFormat(Date date) {
return format(date, DEFAULT);
}
public static Date defaultFormatToDate(String date) {
return formatToDate(DEFAULT, date);
}
/**
* Format a date to the expanded format of "MM/dd/yyyy HH:mm:ss z"
* @param date Date to format
* @return formatted date string
*/
public static String expandedFormat(Date date) {
return format(date, EXPANDED);
}
/**
* Format a string from a give date with the format of "MMM d, yyyy HH:mm:ss z"
* @param date The date to format to a string
* @return formatted string
*/
public static String prettyFormat(Date date) {
return format(date, PRETTY_EXPANDED);
}
/**
* Format a date
* @param date Date to format
* @param format Format in which to convert the date to
* @return formatted date
*/
public static String format(Date date, String format) {
return new SimpleDateFormat(format).format(date);
}
/**
* Convert a string value to a date object
* @param format The format to use in reference to the source
* @param source the source to convert
* @return Date object if the conversion was success; null otherwise
*/
public static Date formatToDate(String format, String source) {
try {
return new SimpleDateFormat(format).parse(source);
} catch (Exception ex) {
return null;
}
}
/**
* Checks to see if a date is within a range with date being now
* Will return false if the provided date or start is null
* end date can be null. Date will only compare against start if this is the case
* @param start starting date
* @param end ending date
* @return true if its between the dates false otherwise
*/
public static boolean inRange(Date start, Date end) {
return inRange(new Date(), start, end);
}
/**
* Checks to see if a date is within a specified date range
* Will return false if the provided date or start is null
* end date can be null. Date will only compare against start if this is the case
* @param date Date to check
* @param start starting date
* @param end ending date
* @return true if its between the dates false otherwise
*/
public static boolean inRange(Date date, Date start, Date end) {
if (date == null) {
return false;
}
if (start == null) {
return false;
}
if (end == null && start.getTime() <= date.getTime()) {
return true;
}
return start.getTime() <= date.getTime() && date.getTime() <= end.getTime();
}
/**
* Sets the date to midnight
* @param date date to set
* @return Date that is set to midnight
*/
public static Date toMidnight(Date date) {
return toMidnight(date, 0);
}
/**
* Sets the date to midnight
* @param date Date to set
* @param addDays Number of days to add (can be negative) to the date when setting it to midnight
* @return date set to midnight for a given date
*/
public static Date toMidnight(Date date, Integer addDays) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, addDays);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
/**
* Get a date with specified minutes ahead
* @param minutesToAdd number of minutes to add to the date (can be negative)
* @return date
*/
public static Date getDate(Integer minutesToAdd) {
return getDate(Calendar.MINUTE, minutesToAdd);
}
/**
* Get a date with specified value added
* @param field Calendar field to add value too
* @param valueToAdd the amount to increase field by (can be negative)
* @return date
*/
public static Date getDate(Integer field, Integer valueToAdd) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
if (valueToAdd != null) {
cal.add(field, valueToAdd);
}
return cal.getTime();
}
}