package io.mangoo.configuration; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.yaml.snakeyaml.Yaml; import com.google.common.io.Resources; import com.google.inject.Singleton; import io.mangoo.core.Application; import io.mangoo.crypto.Crypto; import io.mangoo.enums.Default; import io.mangoo.enums.Jvm; import io.mangoo.enums.Key; import io.mangoo.enums.Mode; import io.mangoo.enums.Required; /** * Main configuration class for all properties configured in application.yaml * * @author svenkubiak * @author williamdunne * */ @Singleton @SuppressWarnings({"rawtypes", "unchecked"}) public class Config { private static final Logger LOG = LogManager.getLogger(Config.class); private final Map<String, String> values = new ConcurrentHashMap<>(16, 0.9F, 1); private boolean decrypted = true; public Config() { prepare(Default.CONFIGURATION_FILE.toString(), Application.getMode()); decrypt(); } public Config(String configFile, Mode mode) { Objects.requireNonNull(configFile, Required.CONFIG_FILE.toString()); Objects.requireNonNull(mode, Required.MODE.toString()); prepare(configFile, mode); decrypt(); } private void prepare(String configFile, Mode mode) { final String configPath = System.getProperty(Jvm.APPLICATION_CONFIG.toString()); Map map; if (StringUtils.isNotBlank(configPath)) { map = (Map) loadConfiguration(configPath, false); LOG.info("Loading application.yaml from: classpath"); } else { map = (Map) loadConfiguration(configFile, true); LOG.info("Loading application.yaml from: " + configFile); } if (map != null) { final Map<String, Object> defaults = (Map<String, Object>) map.get(Default.DEFAULT_CONFIGURATION.toString()); final Map<String, Object> environment = (Map<String, Object>) map.get(mode.toString()); load("", defaults); if (environment != null && !environment.isEmpty()) { load("", environment); } } } private Object loadConfiguration(String path, boolean resource) { InputStream inputStream = null; try { if (resource) { inputStream = Resources.getResource(path).openStream(); } else { inputStream = new FileInputStream(new File(path)); //NOSONAR } } catch (final IOException e) { LOG.error("Failed to load application.yaml", e); } Object object = null; if (inputStream != null) { final Yaml yaml = new Yaml(); object = yaml.load(inputStream); IOUtils.closeQuietly(inputStream); } return object; } /** * Recursively iterates over the yaml file and flatting out the values * * @param parentKey The current key * @param map The map to iterate over */ private void load(String parentKey, Map<String, Object> map) { for (final Map.Entry<String, Object> entry : map.entrySet()) { final String key = entry.getKey(); final Object value = entry.getValue(); if (key != null) { if (value instanceof Map) { load(parentKey + "." + key, (Map<String, Object>) value); } else { if (value == null) { this.values.put(StringUtils.substringAfter(parentKey + "." + key, "."), ""); } else if (("${arg}").equalsIgnoreCase(String.valueOf(value))) { this.values.put(StringUtils.substringAfter(parentKey + "." + key, "."), System.getProperty(entry.getKey())); } else { this.values.put(StringUtils.substringAfter(parentKey + "." + key, "."), String.valueOf(value)); } } } } } /** * Decrypts all encrypted config value */ private void decrypt() { String key = null; Crypto crypto = new Crypto(this); for (final Entry<String, String> entry : this.values.entrySet()) { if (isEncrypted(entry.getValue())) { if (StringUtils.isBlank(key)) { key = getMasterKey(); } if (StringUtils.isNotBlank(key)) { final String decryptedText = crypto.decrypt(StringUtils.substringBetween(entry.getValue(), "cryptex[", "]"), key); if (StringUtils.isNotBlank(decryptedText)) { this.values.put(entry.getKey(), decryptedText); } else { decrypted = false; } } else { LOG.error("Found encrypted config value '" + entry.getKey() + "' but no masterkey was set."); decrypted = false; } } } } /** * @return True if decryption of config values was successful, false otherwise */ public boolean isDecrypted() { return decrypted; } /** * @return The master key for encrypted config value, returns a default value if in test mode */ public String getMasterKey() { String masterkey = System.getProperty(Jvm.APPLICATION_MASTERKEY.toString()); if (StringUtils.isBlank(masterkey)) { String masterkeyFile = this.values.get(Key.APPLICATION_MASTERKEY_FILE.toString()); if (StringUtils.isNotBlank(masterkeyFile)) { try { masterkey = FileUtils.readFileToString(new File(masterkeyFile), Default.ENCODING.toString()); //NOSONAR } catch (final IOException e) { LOG.error("Failed to read master key", e); } } else { LOG.error("Failed to load masterkey file. Please make sure to set a masterkey file if using encrypted config values"); } } return masterkey; } /** * Checks if a value is encrypt by checking for the prefix crpytex * * @param value The value to check * @return True if the value starts with cryptex, false otherwise */ public boolean isEncrypted(String value) { Objects.requireNonNull(value, Required.VALUE.toString()); return value.startsWith("cryptex["); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as String or null if the key is not configured */ public String getString(String key) { return this.values.get(key); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as String or the passed defautlValue if the key is not configured */ public String getString(String key, String defaultValue) { return this.values.getOrDefault(key, defaultValue); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as int or 0 if the key is not configured */ public int getInt(String key) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return 0; } return Integer.parseInt(value); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as long or 0 if the key is not configured */ public long getLong(String key) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return 0; } return Long.parseLong(value); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as int or the passed defautlValue if the key is not configured */ public long getLong(String key, long defaultValue) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return defaultValue; } return Long.parseLong(value); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as int or the passed defautlValue if the key is not configured */ public int getInt(String key, int defaultValue) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return defaultValue; } return Integer.parseInt(value); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as boolean or false if the key is not configured */ public boolean getBoolean(String key) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return false; } return Boolean.parseBoolean(value); } /** * Retrieves a configuration value with the given key * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as boolean or the passed defautlValue if the key is not configured */ public boolean getBoolean(String key, boolean defaultValue) { final String value = this.values.get(key); if (StringUtils.isBlank(value)) { return defaultValue; } return Boolean.parseBoolean(value); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as String or null if the key is not configured */ public String getString(Key key) { return getString(key.toString()); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as String or the passed defautlValue if the key is not configured */ public String getString(Key key, String defaultValue) { return getString(key.toString(), defaultValue); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as long or null if the key is not configured */ public long getLong(Key key) { return getLong(key.toString()); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as long or the passed defautlValue if the key is not configured */ public long getLong(Key key, long defaultValue) { return getLong(key.toString(), defaultValue); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as int or 0 if the key is not configured */ public int getInt(Key key) { return getInt(key.toString()); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as int or the passed defautlValue if the key is not configured */ public int getInt(Key key, int defaultValue) { return getInt(key.toString(), defaultValue); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @return The configured value as boolean or false if the key is not configured */ public boolean getBoolean(Key key) { return getBoolean(key.toString()); } /** * Retrieves a configuration value with the given key constant (e.g. Key.APPLICATION_NAME) * * @param key The key of the configuration value (e.g. application.name) * @param defaultValue The default value to return of no key is found * @return The configured value as boolean or the passed defautlValue if the key is not configured */ public boolean getBoolean(Key key, boolean defaultValue) { return getBoolean(key.toString(), defaultValue); } /** * @return All configuration options of the current environment */ public Map<String, String> getAllConfigurations() { return new ConcurrentHashMap(this.values); } /** * Checks if the application.conf stored in conf/application.conf contains an application * secret property (application.secret) that has at least 32 characters (256-Bit) * * @return True if the configuration contains an application.secret property with at least 32 characters */ public boolean hasValidSecret() { final String secret = getApplicationSecret(); return StringUtils.isNotBlank(secret) && secret.length() >= Default.APPLICATION_SECRET_MIN_LENGTH.toInt(); } /** * @return application.name from application.yaml */ public String getApplicationName() { return getString(Key.APPLICATION_NAME); } /** * @return default name of flash cookie name */ public String getFlashCookieName() { return Default.FLASH_COOKIE_NAME.toString(); } /** * @return cookie.name from application.yaml or default value if undefined */ public String getSessionCookieName() { return getString(Key.COOKIE_NAME, Default.COOKIE_NAME.toString()); } /** * @return application.secret from application.yaml */ public String getApplicationSecret() { return getString(Key.APPLICATION_SECRET); } /** * @return auth.cookie.name from application.yaml or default value if undefined */ public String getAuthenticationCookieName() { return getString(Key.AUTH_COOKIE_NAME, Default.AUTH_COOKIE_NAME.toString()); } /** * @return auth.cookie.expires from application.yaml or default value if undefined */ public long getAuthenticationExpires() { return getLong(Key.AUTH_COOKIE_EXPIRES, Default.AUTH_COOKIE_EXPIRES.toLong()); } /** * @return cookie.expires from application.yaml or default value if undefined */ public long getSessionExpires() { return getLong(Key.COOKIE_EXPIRES, Default.COOKIE_EXPIRES.toLong()); } /** * @return cookie.secure from application.yaml or default value if undefined */ public boolean isSessionCookieSecure() { return getBoolean(Key.COOKIE_SECURE, Default.COOKIE_SECURE.toBoolean()); } /** * @return auth.cookie.secure from application.yaml or default value if undefined */ public boolean isAuthenticationCookieSecure() { return getBoolean(Key.AUTH_COOKIE_SECURE, Default.AUTH_COOKIE_SECURE.toBoolean()); } /** * @author William Dunne * @return cookie.i18n.name from application.yaml or default value if undefined */ public String getI18nCookieName() { return getString(Key.COOKIE_I18N_NAME, Default.COOKIE_I18N_NAME.toString()); } /** * @return same value as isSessionCookieSecure() */ public boolean isFlashCookieSecure() { return isSessionCookieSecure(); } /** * @return application.language from application.yaml or default value if undefined */ public String getApplicationLanguage() { return getString(Key.APPLICATION_LANGUAGE, Default.LANGUAGE.toString()); } /** * @return auth.cookie.encrypt from application.yaml or default value if undefined */ public boolean isAuthenticationCookieEncrypt() { return getBoolean(Key.AUTH_COOKIE_ENCRYPT, Default.AUTH_COOKIE_ENCRYPT.toBoolean()); } /** * @return auth.cookie.version from application.yaml or default value if undefined */ public String getAuthCookieVersion() { return getString(Key.AUTH_COOKIE_VERSION, Default.AUTH_COOKIE_VERSION.toString()); } /** * @return cookie.version from application.yaml or default value if undefined */ public String getCookieVersion() { return getString(Key.COOKIE_VERSION, Default.COOKIE_VERSION.toString()); } /** * @return scheduler.autostart from application.yaml or default value if undefined */ public boolean isSchedulerAutostart() { return getBoolean(Key.SCHEDULER_AUTOSTART, Default.SCHEDULER_AUTOSTART.toBoolean()); } /** * @return application.admin.username from application.yaml or null if undefined */ public String getAdminAuthenticationUser() { return getString(Key.APPLICATION_ADMIN_USERNAME); } /** * @return application.admin.password from application.yaml or null if undefined */ public String getAdminAuthenticationPassword() { return getString(Key.APPLICATION_ADMIN_PASSWORD); } /** * @return scheduler.package from application.yaml or default value if undefined */ public String getSchedulerPackage() { return getString(Key.SCHEDULER_PACKAGE, Default.SCHEDULER_PACKAGE.toString()); } /** * @return cookie.encryption from application.yaml or default value if undefined */ public boolean isSessionCookieEncrypt() { return getBoolean(Key.COOKIE_ENCRYPTION, Default.COOKIE_ENCRYPTION.toBoolean()); } /** * @return auth.cookie.remember.expires from application.yaml or default value if undefined */ public long getAuthenticationRememberExpires() { return getLong(Key.AUTH_COOKIE_REMEMBER_EXPIRES, Default.AUTH_COOKIE_REMEMBER_EXPIRES.toLong()); } /** * @return execution.threadpool from application.yaml or default value if undefined */ public int getExecutionPool() { return getInt(Key.APPLICATION_THREADPOOL, Default.EXECUTION_THREADPOOL.toInt()); } /** * @return application.controller from application.yaml or default value if undefined */ public String getControllerPackage() { return getString(Key.APPLICATION_CONTROLLER, Default.APPLICATION_CONTROLLER.toString()); } /** * @return templateengine.class from application.yaml */ public String getTemplateEngineClass() { return getString(Key.APPLICATION_TEMPLATEENGINE, Default.TEMPLATE_ENGINE_CLASS.toString()); } /** * @return application.minify.js or default value if undefined */ public boolean isMinifyJS() { return getBoolean(Key.APPLICATION_MINIFY_JS, false); } /** * @return application.minify.css or default value if undefined */ public boolean isMinifyCSS() { return getBoolean(Key.APPLICATION_MINIFY_CSS, false); } /** * @return application.preprocess.sass or default value if undefined */ public boolean isPreprocessSass() { return getBoolean(Key.APPLICATION_PREPROCESS_SASS, false); } /** * @return application.preprocess.less or default value if undefined */ public boolean isPreprocessLess() { return getBoolean(Key.APPLICATION_PREPROCESS_LESS, false); } /** * @return application.assets.path (for testing purposes only) */ public String getAssetsPath() { return Default.ASSETS_PATH.toString(); } /** * * @return application.admin.enable or default value if undefined */ public boolean isAdminEnabled() { return getBoolean(Key.APPLICATION_ADMIN_ENABLE, false); } /** * @return smtp.host or default value if undefined */ public String getSmtpHost() { return getString(Key.SMTP_HOST, Default.SMTP_HOST.toString()); } /** * @return smtp.port or default value if undefined */ public int getSmtpPort() { return getInt(Key.SMTP_PORT, Default.SMTP_PORT.toInt()); } /** * @return smtp.ssl or default value if undefined */ public boolean isSmtpSSL() { return getBoolean(Key.SMTP_SSL, Default.SMTP_SSL.toBoolean()); } /** * @return smtp.username or null value if undefined */ public String getSmtpUsername() { return getString(Key.SMTP_USERNAME, null); } /** * @return smtp.username or null value if undefined */ public String getSmtpPassword() { return getString(Key.SMTP_PASSWORD, null); } /** * @return smtp.from or default value if undefined */ public String getSmtpFrom() { return getString(Key.SMTP_FROM, Default.SMTP_FROM.toString()); } /** * @return jvm property http.host or connector.http.host or null if undefined */ public String getConnectorHttpHost() { String httpHost = System.getProperty(Jvm.HTTP_HOST.toString()); if (StringUtils.isNotBlank(httpHost)) { return httpHost; } return getString(Key.CONNECTOR_HTTP_HOST, null); } /** * @return jvm property http.port or connector.http.port or 0 if undefined */ public int getConnectorHttpPort() { String httpPort = System.getProperty(Jvm.HTTP_PORT.toString()); if (StringUtils.isNotBlank(httpPort)) { return Integer.parseInt(httpPort); } return getInt(Key.CONNECTOR_HTTP_PORT, 0); } /** * @return jvm property ajp.host or connector.ajp.host or null if undefined */ public String getConnectorAjpHost() { String ajpHost = System.getProperty(Jvm.AJP_HOST.toString()); if (StringUtils.isNotBlank(ajpHost)) { return ajpHost; } return getString(Key.CONNECTOR_AJP_HOST, null); } /** * @return jvm property ajp.port or connector.ajp.port or 0 if undefined */ public int getConnectorAjpPort() { String ajpPort = System.getProperty(Jvm.AJP_PORT.toString()); if (StringUtils.isNotBlank(ajpPort)) { return Integer.parseInt(ajpPort); } return getInt(Key.CONNECTOR_AJP_PORT, 0); } /** * @return application.jwt.signkey or application secret if undefined */ public String getJwtsSignKey() { return getString(Key.APPLICATION_JWT_SIGNKEY, getApplicationSecret()); } /** * @return application.jwt.encrypt or default value if undefined */ public boolean isJwtsEncrypted() { return getBoolean(Key.APPLICATION_JWT_ENCRYPT, Default.APPLICATION_JWT_ENCRYPT.toBoolean()); } /** * @return application.jwt.encryptionkey or application secret if undefined */ public String getJwtsEncryptionKey() { return getString(Key.APPLICATION_JWT_ENCRYPTION_KEY, getApplicationSecret()); } /** * * @return application.headers.xssprotection or default value if undefined */ public int getXssProectionHeader() { return getInt(Key.APPLICATION_HEADERS_XSSPROTECTION, Default.APPLICATION_HEADERS_XSSPROTECTION.toInt()); } /** * @return application.headers.xcontenttypeoptions or default value if undefined */ public String getXContentTypeOptionsHeader() { return getString(Key.APPLICATION_HEADERS_XCONTENTTYPEOPTIONS, Default.APPLICATION_HEADERS_XCONTENTTYPEOPTIONS.toString()); } /** * @return application.headers.xframeoptions or default value if undefined */ public String getXFrameOptionsHeader() { return getString(Key.APPLICATION_HEADERS_XFRAMEOPTIONS, Default.APPLICATION_HEADERS_XFRAMEOPTIONS.toString()); } /** * @return application.headers.server or default value if undefined */ public String getServerHeader() { return getString(Key.APPLICATION_HEADERS_SERVER, Default.APPLICATION_HEADERS_SERVER.toString()); } /** * @return application.headers.contentsecuritypolicy or default value if undefined */ public String getContentSecurityPolicyHeader() { return getString(Key.APPLICATION_HEADERS_CONTENTSECURITYPOLICY, Default.APPLICATION_HEADERS_CONTENTSECURITYPOLICY.toString()); } /** * @return cache.cluster.enabled or default value if undefined */ public boolean isClusteredCached() { return getBoolean(Key.CACHE_CLUSTER_ENABLE, Default.CACHE_CLUSTER_ENABLE.toBoolean()); } /** * @return authentication.lock or default value if undefined */ public int getAuthenticationLock() { return getInt(Key.AUTH_LOCK, Default.AUTH_LOCK.toInt()); } /** * @return cache.cluster.url or null if undefined */ public String getCacheClusterUrl() { return getString(Key.CACHE_CLUSTER_URL, null); } /** * @return application.headers.refererpolicy or default value if undefined */ public String getRefererPolicy() { return getString(Key.APPLICATION_HEADERS_REFERERPOLICY, Default.APPLICATION_HEADERS_REFERERPOLICY.toString()); } }