All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mangoo.core.Config Maven / Gradle / Ivy

The newest version!
package io.mangoo.core;

import com.google.common.io.Resources;
import com.google.re2j.Pattern;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.mangoo.constants.Default;
import io.mangoo.constants.Key;
import io.mangoo.crypto.Crypto;
import io.mangoo.exceptions.MangooEncryptionException;
import jakarta.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

@Singleton
public class Config {
    private static final Logger LOG = LogManager.getLogger(Config.class);
    private static final String CONFIG_FILE = "config.yaml";
    private static final String CRYPTEX_TAG = "cryptex{";
    private static final String ARG_TAG = "arg{}";
    private final Map values = new ConcurrentHashMap<>();
    private Pattern corsUrl;
    private Pattern corsAllowOrigin;
    private boolean decrypted = true;
    private boolean valid;

    public Config() {
        load();
    }

    @SuppressWarnings("unchecked")
    private void load() {
        try (var inputStream = getConfigInputStream()){
            var loaderOptions = new LoaderOptions();
            loaderOptions.setAllowDuplicateKeys(false);
            loaderOptions.setMaxAliasesForCollections(5);

            var yaml = new Yaml(loaderOptions);
            Map config = yaml.load(inputStream);

            Map defaultConfig = (Map) config.get("default");
            Map environments = (Map) config.get("environments");

            String activeEnv = Application.getMode().toString().toLowerCase(Locale.ENGLISH);

            Map activeEnvironment = (Map) environments.get(activeEnv);
            if (activeEnvironment != null) {
                Map mergedConfig = new HashMap<>(defaultConfig);
                mergeMaps(mergedConfig, activeEnvironment);

                Map falttenedMap = flattenMap(mergedConfig);
                falttenedMap.forEach(this::parse);

                valid = true;
            } else {
                LOG.error("Active environment '{}' not found in config.yaml", activeEnv);
            }
        } catch (Exception e) {
            LOG.error("Failed to load config.yaml", e);
        }
    }

    @SuppressFBWarnings(justification = "Intentionally used to access the file system", value = {"PATH_TRAVERSAL_IN", "URLCONNECTION_SSRF_FD"})
    private InputStream getConfigInputStream() throws IOException {
        String configPath = System.getProperty(Key.APPLICATION_CONFIG);
        InputStream inputStream;

        if (StringUtils.isNotBlank(configPath)) {
            inputStream = Files.newInputStream(Path.of(configPath));
        } else {
            inputStream = Resources.getResource(CONFIG_FILE).openStream();
        }

        return inputStream;
    }

    @SuppressWarnings("unchecked")
    private void mergeMaps(Map baseMap, Map overrideMap) {
        overrideMap.forEach((key, value) -> {
            if (value instanceof Map && baseMap.get(key) instanceof Map) {
                mergeMaps((Map) baseMap.get(key), (Map) value);
            } else {
                baseMap.put(key, value);
            }
        });
    }

    private Map flattenMap(Map map) {
        Map flatMap = new HashMap<>();
        flattenMapHelper(map, "", flatMap);
        return flatMap;
    }

    @SuppressWarnings("unchecked")
    private void flattenMapHelper(Map map, String prefix, Map flatMap) {
        map.forEach((key, value) -> {
            String newKey = prefix.isEmpty() ? key : prefix + "." + key;

            if (value instanceof Map) {
                flattenMapHelper((Map) value, newKey, flatMap);
            } else {
                flatMap.put(newKey, value != null ? value.toString() : Strings.EMPTY);
            }
        });
    }

    /**
     * Parses a given property key and value and checks if the value comes from
     * a system property and maybe decrypts the value
     *
     * @param key The property key
     * @param value The property value
     */
    private void parse(String key, String value) {
        if (ARG_TAG.equals(value)) {
            String propertyValue = System.getProperty(key);

            if (StringUtils.isNotBlank(propertyValue) && propertyValue.startsWith(CRYPTEX_TAG)) {
                propertyValue = decrypt(key, propertyValue);
            }

            if (StringUtils.isNotBlank(propertyValue)) {
                values.put(key, propertyValue);
            }
        } else if (value.startsWith("arg{")) {
            value = StringUtils.substringBetween(value, "arg{", "}");

            if (StringUtils.isNotBlank(value)) {
                values.put(key, value);
            }
        } else if (value.startsWith(CRYPTEX_TAG)) {
            values.put(key, decrypt(key, value));
        } else {
            values.put(key, value);
        }
    }

    /**
     * Decrypts a given property key and rewrites it to props
     *
     * @param value The encrypted value to decrypt
     */
    private String decrypt(String key, String value) {
        var crypto = new Crypto();

        String keyFile = System.getProperty(Key.APPLICATION_PRIVATE_KEY);
        if (StringUtils.isNotBlank(keyFile)) {
            try (Stream lines = Files.lines(Path.of(keyFile))) { //NOSONAR KeyFile can intentionally come from user input
                String encryptionKey = lines.findFirst().orElse(null);
                if (StringUtils.isNotBlank(encryptionKey)) {
                    var privateKey = crypto.getPrivateKeyFromString(encryptionKey);
                    var cryptex = StringUtils.substringBetween(value, CRYPTEX_TAG, "}");

                    if (privateKey != null && StringUtils.isNotBlank(cryptex)) {
                        return crypto.decrypt(cryptex, privateKey);
                    } else {
                        LOG.error("Failed to decrypt an encrypted config value");
                        decrypted = false;
                    }
                }
            } catch (IOException | SecurityException | MangooEncryptionException e) {
                LOG.error("Failed to decrypt an encrypted config value", e);
                decrypted = false;
            }
        } else {
            LOG.error("{} has an encrypted value in config.yaml but private key for decryption is missing", key);
            decrypted = false;
        }

        return Strings.EMPTY;
    }

    /**
     * Validates if each config value has been decrypted and parsed correctly
     */
    public void validate() {
        for (Map.Entry entry : values.entrySet()) {
            String value = entry.getValue();
            if (value != null && (value.startsWith(CRYPTEX_TAG) || value.startsWith(ARG_TAG)) ) {
                LOG.error("{} has not been decrypted or parsed correctly", entry.getKey());
                valid = false;
            }
        }
    }

    /**
     * Converts config values to standard java properties
     *
     * @return Properties instance with config values
     */
    public Properties toProperties() {
        var properties = new Properties();
        properties.putAll(values);

        return properties;
    }

    /**
     * @return True if decryption of config values was successful, false otherwise
     */
    public boolean isDecrypted() {
        return decrypted;
    }

    /**
     * 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 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 defaultValue if the key is not configured
     */
    public String getString(String key, String defaultValue) {
        return 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 = 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 = 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 defaultValue if the key is not configured
     */
    public long getLong(String key, long defaultValue) {
        final String value = 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 = 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 = values.get(key);
        if (StringUtils.isBlank(value)) {
            return Boolean.FALSE;
        }

        return Boolean.valueOf(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 defaultValue if the key is not configured
     */
    public Boolean getBoolean(String key, Boolean defaultValue) {
        final String value = values.get(key);
        if (StringUtils.isBlank(value)) {
            return defaultValue;
        }

        return Boolean.valueOf(value);
    }

    /**
     * @return All configuration options of the current environment
     */
    public Map getAllConfigurations() {
        return new ConcurrentHashMap<>(values);
    }

    /**
     * @return application.name from config.yaml
     */
    public String getApplicationName() {
        return getString(Key.APPLICATION_NAME, Default.APPLICATION_NAME);
    }

    /**
     * @return flash.cookie.name or default value if undefined
     */
    public String getFlashCookieName() {
        return getString(Key.FLASH_COOKIE_NAME, Default.FLASH_COOKIE_NAME);
    }

    /**
     * @return session.cookie.name from config.yaml or default value if undefined
     */
    public String getSessionCookieName() {
        return getString(Key.SESSION_COOKIE_NAME, Default.SESSION_COOKIE_NAME);
    }

    /**
     * @return application.secret from config.yaml
     */
    public String getApplicationSecret() {
        return getString(Key.APPLICATION_SECRET);
    }

    /**
     * @return application.publicKey from config.yaml
     */
    public String getApplicationPublicKey() {
        return getString(Key.APPLICATION_PUBLIC_KEY);
    }

    /**
     * @return authentication.cookie.name from config.yaml or default value if undefined
     */
    public String getAuthenticationCookieName() {
        return getString(Key.AUTHENTICATION_COOKIE_NAME, Default.AUTHENTICATION_COOKIE_NAME);
    }

    /**
     * @return session.cookie.token.expires from config.yaml or default value if undefined
     */
    public long getSessionCookieTokenExpires() {
        return getLong(Key.SESSION_COOKIE_TOKEN_EXPIRES, Default.SESSION_COOKIE_TOKEN_EXPIRES);
    }

    /**
     * @return session.cookie.secure from config.yaml or default value if undefined
     */
    public boolean isSessionCookieSecure() {
        return getBoolean(Key.SESSION_COOKIE_SECURE, Default.SESSION_COOKIE_SECURE);
    }

    /**
     * @return authentication.cookie.secure from config.yaml or default value if undefined
     */
    public boolean isAuthenticationCookieSecure() {
        return getBoolean(Key.AUTHENTICATION_COOKIE_SECURE, Default.AUTHENTICATION_COOKIE_SECURE);
    }

    /**
     * @return i18n.cookie.name from config.yaml or default value if undefined
     */
    public String getI18nCookieName() {
        return getString(Key.I18N_COOKIE_NAME, Default.I18N_COOKIE_NAME);
    }

    /**
     * @return calls isSessionCookieSecure()
     */
    public boolean isFlashCookieSecure() {
        return isSessionCookieSecure();
    }

    /**
     * @return application.language from config.yaml or default value if undefined
     */
    public String getApplicationLanguage() {
        return getString(Key.APPLICATION_LANGUAGE, Default.APPLICATION_LANGUAGE);
    }

    /**
     * @return application.admin.username from config.yaml or null if undefined
     */
    public String getApplicationAdminUsername() {
        return getString(Key.APPLICATION_ADMIN_USERNAME, null);
    }

    /**
     * @return application.admin.password from config.yaml or null if undefined
     */
    public String getApplicationAdminPassword() {
        return getString(Key.APPLICATION_ADMIN_PASSWORD, null);
    }

    /**
     * @return authentication.cookie.remember.expires from config.yaml or default value if undefined
     */
    public long getAuthenticationCookieRememberExpires() {
        return getLong(Key.AUTHENTICATION_COOKIE_REMEMBER_EXPIRES, Default.AUTHENTICATION_COOKIE_REMEMBER_EXPIRES);
    }

    /**
     * @return application.controller from config.yaml or default value if undefined
     */
    public String getApplicationController() {
        return getString(Key.APPLICATION_CONTROLLER, Default.APPLICATION_CONTROLLER);
    }

    /**
     * @return application.admin.enable or default value if undefined
     */
    public boolean isApplicationAdminEnable() {
        return getBoolean(Key.APPLICATION_ADMIN_ENABLE, Default.APPLICATION_ADMIN_ENABLE);
    }

    /**
     * @return smtp.host or default value if undefined
     */
    public String getSmtpHost() {
        return getString(Key.SMTP_HOST, Default.SMTP_HOST);
    }

    /**
     * @return smtp.port or default value if undefined
     */
    public int getSmtpPort() {
        return getInt(Key.SMTP_PORT, Default.SMTP_PORT);
    }

    /**
     * @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);
    }

    /**
     * @return jvm property http.host or connector.http.host or null if undefined
     */
    public String getConnectorHttpHost() {
        return getString(Key.CONNECTOR_HTTP_HOST, null);
    }

    /**
     * @return jvm property http.port or connector.http.port or 0 if undefined
     */
    public int getConnectorHttpPort() {
        return getInt(Key.CONNECTOR_HTTP_PORT, 0);
    }

    /**
     * @return jvm property ajp.host or connector.ajp.host or null if undefined
     */
    public String getConnectorAjpHost() {
        return getString(Key.CONNECTOR_AJP_HOST, null);
    }

    /**
     * @return jvm property ajp.port or connector.ajp.port or 0 if undefined
     */
    public int getConnectorAjpPort() {
        return getInt(Key.CONNECTOR_AJP_PORT, 0);
    }

    /**
     * @return metrics.enable or default value if undefined
     */
    public boolean isMetricsEnable() {
        return getBoolean(Key.METRICS_ENABLE, Default.METRICS_ENABLE);
    }

    /**
     * @return authentication.lock or default value if undefined
     */
    public int getAuthenticationLock() {
        return getInt(Key.AUTHENTICATION_LOCK, Default.AUTHENTICATION_LOCK);
    }

    /**
     * @return undertow.maxentitysize or default value if undefined
     */
    public long getUndertowMaxEntitySize() {
        return getLong(Key.UNDERTOW_MAX_ENTITY_SIZE, Default.UNDERTOW_MAX_ENTITY_SIZE);
    }

    /**
     * @return session.cookie.secret or application secret if undefined
     */
    public String getSessionCookieSecret() {
        return getString(Key.SESSION_COOKIE_SECRET, getApplicationSecret());
    }

    /**
     * @return authentication.cookie.secret or application secret if undefined
     */
    public String getAuthenticationCookieSecret() {
        return getString(Key.AUTHENTICATION_COOKIE_SECRET, getApplicationSecret());
    }

    /**
     * @return flash.cookie.secret or application secret if undefined
     */
    public String getFlashCookieSecret() {
        return getString(Key.FLASH_COOKIE_SECRET, getApplicationSecret());
    }

    /**
     * @return scheduler.enable or default value if undefined
     */
    public boolean isSchedulerEnabled() {
        return getBoolean(Key.SCHEDULER_ENABLE, Default.SCHEDULER_ENABLE);
    }

    /**
     * @return application.admin.secret or null if undefined
     */
    public String getApplicationAdminSecret() {
        return getString(Key.APPLICATION_ADMIN_SECRET, null);
    }

    /**
     * @return smtp.debug or default value if undefined
     */
    public boolean isSmtpDebug() {
        return getBoolean(Key.SMTP_DEBUG, Default.SMTP_DEBUG);
    }

    /**
     * @return cors.enable or default value if undefined
     */
    public boolean isCorsEnable() {
        return getBoolean(Key.CORS_ENABLE, Default.CORS_ENABLE);
    }

    /**
     * @return cors.urlpattern as compiled pattern or default value if undefined
     */
    public Pattern getCorsUrlPattern() {
        if (corsUrl == null) {
            corsUrl = Pattern.compile(getString(Key.CORS_URL_PATTERN, Default.CORS_URL_PATTERN));
        }
        return corsUrl;
    }

    /**
     * @return cors.policyclass as compiled pattern or default value if undefined
     */
    public Pattern getCorsAllowOrigin() {
        if (corsAllowOrigin == null) {
            corsAllowOrigin = Pattern.compile(getString(Key.CORS_ALLOW_ORIGIN, Default.CORS_ALLOW_ORIGIN));
        }

        return corsAllowOrigin;
    }

    /**
     * @return cors.headers.allowcredentials or default value if undefined
     */
    public String getCorsHeadersAllowCredentials() {
        return getString(Key.CORS_HEADERS_ALLOW_CREDENTIALS, Default.CORS_HEADERS_ALLOW_CREDENTIALS.toString());
    }

    /**
     * @return cors.headers.allowheaders or default value if undefined
     */
    public String getCorsHeadersAllowHeaders() {
        return getString(Key.CORS_HEADERS_ALLOW_HEADERS, Default.CORS_HEADERS_ALLOW_HEADERS);
    }

    /**
     * @return cors.headers.allowheaders or default value if undefined
     */
    public String getCorsHeadersAllowMethods() {
        return getString(Key.CORS_HEADERS_ALLOW_METHODS, Default.CORS_HEADERS_ALLOW_METHODS);
    }

    /**
     * @return cors.headers.exposeheaders or default value if undefined
     */
    public String getCorsHeadersExposeHeaders() {
        return getString(Key.CORS_HEADERS_EXPOSE_HEADERS, Default.CORS_HEADERS_EXPOSE_HEADERS);
    }

    /**
     * @return cors.headers.maxage or default value if undefined
     */
    public String getCorsHeadersMaxAge() {
        return getString(Key.CORS_HEADERS_MAX_AGE, Default.CORS_HEADERS_MAX_AGE);
    }

    /**
     * @return persistence.mongo.host or default value if undefined
     * @param prefix The prefix to use
     */
    public String getMongoHost(String prefix) {
        return getString(prefix + Key.PERSISTENCE_MONGO_HOST, Default.PERSISTENCE_MONGO_HOST);
    }

    /**
     * @return persistence.mongo.port or default value if undefined
     * @param prefix The prefix to use
     */
    public int getMongoPort(String prefix) {
        return getInt(prefix + Key.PERSISTENCE_MONGO_PORT, Default.PERSISTENCE_MONGO_PORT);
    }

    /**
     * @return persistence.mongo.username or null if undefined
     * @param prefix The prefix to use
     */
    public String getMongoUsername(String prefix) {
        return getString(prefix + Key.PERSISTENCE_MONGO_USERNAME, null);
    }

    /**
     * @return persistence.mongo.password or null if undefined
     * @param prefix The prefix to use
     */
    public String getMongoPassword(String prefix) {
        return getString(prefix + Key.PERSISTENCE_MONGO_PASSWORD, null);
    }

    /**
     * @return persistence.mongo.authdb or null if undefined
     * @param prefix The prefix to use
     */
    public String getMongoAuthDB(String prefix) {
        return getString(prefix + Key.PERSISTENCE_MONGO_AUTHDB, null);
    }

    /**
     * @return persistence.mongo.auth or default value if undefined
     * @param prefix The prefix to use
     */
    public Boolean isMongoAuth(String prefix) {
        return getBoolean(prefix + Key.PERSISTENCE_MONGO_AUTH, Default.PERSISTENCE_MONGO_AUTH);
    }

    /**
     * @return persistence.mongo.dbname or default value if undefined
     * @param prefix The prefix to use
     */
    public String getMongoDbName(String prefix) {
        return getString(prefix + Key.PERSISTENCE_MONGO_DBNAME, Default.PERSISTENCE_MONGO_DBNAME);
    }

    /**
     * @return persistence.mongo.embedded or default value if undefined
     * @param prefix The prefix to use
     */
    public Boolean isMongoEmbedded(String prefix) {
        return getBoolean(prefix + Key.PERSISTENCE_MONGO_EMBEDDED, Default.PERSISTENCE_MONGO_EMBEDDED);
    }

    /**
     * @return session.cookie.expires or default value if undefined
     */
    public Boolean isSessionCookieExpires() {
        return getBoolean(Key.SESSION_COOKIE_EXPIRES, Default.SESSION_COOKIE_EXPIRES);
    }

    /**
     * @return authentication.cookie.expires or default value if undefined
     */
    public boolean isAuthenticationCookieExpires() {
        return getBoolean(Key.AUTHENTICATION_COOKIE_EXPIRES, Default.AUTHENTICATION_COOKIE_EXPIRES);
    }

    /**
     * @return authentication.cookie.expires or default value if undefined
     */
    public long getAuthenticationCookieTokenExpires() {
        return getLong(Key.AUTHENTICATION_COOKIE_TOKEN_EXPIRES, Default.AUTHENTICATION_COOKIE_TOKEN_EXPIRES);
    }

    /**
     * @return smtp.authentication or default value if undefined
     */
    public boolean isSmtpAuthentication() {
        return getBoolean(Key.SMTP_AUTHENTICATION, Default.SMTP_AUTHENTICATION);
    }

    /**
     * @return mongo.enable or default value if undefined
     */
    public boolean isPersistenceEnabled() {
        return getBoolean(Key.PERSISTENCE_ENABLE, Default.PERSISTENCE_ENABLE);
    }

    /**
     * @return smtp.protocol or default value if undefined
     */
    public String getSmtpProtocol() {
        return getString(Key.SMTP_PROTOCOL, Default.SMTP_PROTOCOL);
    }

    /**
     * @return authentication.origin or default value if undefined
     */
    public boolean isAuthOrigin() {
        return getBoolean(Key.AUTHENTICATION_ORIGIN, Default.AUTHENTICATION_ORIGIN);
    }

    /**
     * @return application.admin.locale or default value if undefined
     */
    public Object getApplicationAdminLocale() {
        return getString(Key.APPLICATION_ADMIN_LOCALE, Default.APPLICATION_ADMIN_LOCALE);
    }

    /**
     * @return token.secret or null if undefined
     */
    public String getTokenSecret() {
        return getString(Key.APPLICATION_PASETO_SECRET, null);
    }

    public boolean isValid() {
        return valid;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy