#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace rublon { class ConfigurationFactory; enum class FailMode { bypass, deny }; class Configuration { private: std::pmr::memory_resource * memoryResource; public: Configuration(std::pmr::memory_resource * mr = memory::default_resource()) : memoryResource{mr} {} // change to StaticString std::pmr::string systemToken{memoryResource}; std::pmr::string secretKey{memoryResource}; std::pmr::string apiServer{memoryResource}; int prompt{}; bool enablePasswdEmail{}; bool logging{}; bool autopushPrompt{}; FailMode failMode{}; bool nonInteractiveMode{}; std::optional< std::pmr::string > proxyType{memoryResource}; std::optional< std::pmr::string > proxyServer{memoryResource}; std::optional< std::pmr::string > proxyUsername{memoryResource}; std::optional< std::pmr::string > proxyPass{memoryResource}; std::optional< int > proxyPort{}; bool proxyAuthRequired{}; // defaulted bool proxyEnabled{}; // defaulted }; class ConfigurationReader { public: ConfigurationReader(std::pmr::memory_resource * memResource = memory::default_resource()) : memoryResource(memResource) {} // Load config from file path bool loadFromFile(std::string_view filepath) { using namespace memory::literals; memory::MonotonicStackResource< 8_kB > stackResource; std::ifstream file(filepath.data()); if(not file.good()) return false; std::pmr::string line{&stackResource}; line.reserve(8000); // allocate full stack to line while(std::getline(file, line)) { std::pmr::string key{memoryResource}; std::pmr::string value{memoryResource}; details::trimInPlace(line); if(!line.length()) continue; if(line[0] == '#' || line[0] == ';') continue; auto posEqual = line.find('='); key = line.substr(0, posEqual); value = line.substr(posEqual + 1); keyValues[std::move(key)] = std::move(value); } return true; } // Load values into Configuration object, with defaults provided tl::expected< bool, ConfigurationError > applyTo(Configuration & config) const { using string = std::pmr::string; // Helper lambdas for conversion auto getStringOpt = [&](const string & key) -> std::optional< std::pmr::string > { auto it = keyValues.find(key); if(it == keyValues.end()) { return std::nullopt; } return string{it->second.data(), it->second.size(), memoryResource}; }; auto getStringReq = [&](const string & key) -> tl::expected< std::pmr::string, ConfigurationError > { auto val = getStringOpt(key); if(val.has_value()) { if(val->empty()) { return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueEmpty}; } return std::move(val.value()); } return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound}; }; auto getInt = [&](const string & key) -> std::optional< int > { auto it = keyValues.find(key); if(it == keyValues.end()) return std::nullopt; return conv::to_uint32opt(it->second); }; auto getBool = [&](const string & key) -> std::optional< bool > { memory::MonotonicStackResource< 64 > memoryResource; auto it = keyValues.find(key); if(it == keyValues.end()) return std::nullopt; if(it->second.size() > 5) { log(LogLevel::Warning, "Configuration value %s is too long, please check", key.c_str()); return std::nullopt; } std::pmr::string val{&memoryResource}; val = it->second; std::transform(val.begin(), val.end(), val.begin(), [](unsigned char c) { return static_cast< char >(std::tolower(c)); }); if(val == "1" || val == "true" || val == "yes" || val == "on") return true; if(val == "0" || val == "false" || val == "no" || val == "off") return false; return std::nullopt; }; auto getFailMode = [&](const string & key) -> std::optional< FailMode > { auto it = keyValues.find(key); if(it == keyValues.end()) return std::nullopt; auto val = it->second; if(val == "bypass") return FailMode::bypass; if(val == "deny") return FailMode::deny; return std::nullopt; }; /// NOTE: // getStringOpt can return a valid empty string, for example configuration entry like // option= // will return a optional val which contains empty string. // getStringReq on the other hand, returns error in case when // * configuration is not found -> RequiredValueNotFound // * configuration value is empty -> RequiredValueEmpty // Reading required fields if(auto val = getStringReq("systemToken"); not val.has_value()) return tl::unexpected{val.error()}; else config.systemToken = std::move(val.value()); if(auto val = getStringReq("secretKey"); not val.has_value()) return tl::unexpected{val.error()}; else config.secretKey = std::move(val.value()); if(auto val = getStringReq("rublonApiServer"); not val.has_value()) return tl::unexpected{val.error()}; else config.apiServer = std::move(val.value()); // optional configuration options config.prompt = getInt("prompt").value_or(1); config.enablePasswdEmail = getBool("enablePasswdEmail").value_or(true); config.logging = getBool("logging").value_or(true); config.autopushPrompt = getBool("autopushPrompt").value_or(false); config.nonInteractiveMode = getBool("nonInteractiveMode").value_or(false); config.failMode = getFailMode("failMode").value_or(FailMode::deny); // reading proxy configuration config.proxyEnabled = getBool("proxyEnabled").value_or(false); config.proxyType = getStringOpt("proxyType"); config.proxyServer = getStringOpt("proxyServer"); if(config.proxyEnabled) { if(not config.proxyType or config.proxyType->empty()) { log(LogLevel::Error, "Proxy is enabled but proxy type is not present or empty"); return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration}; } if(not config.proxyServer or config.proxyServer->empty()) { log(LogLevel::Error, "Proxy is enabled but proxy server is not present or empty"); return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration}; } } config.proxyAuthRequired = getBool("proxyAuthRequired").value_or(false); config.proxyUsername = getStringOpt("proxyUsername"); config.proxyPass = getStringOpt("proxyPass"); if(config.proxyAuthRequired) { if(not config.proxyUsername or config.proxyUsername->empty()) { log(LogLevel::Error, "Proxy auth is required but proxy proxy username is not present or empty"); return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration}; } if(not config.proxyPass) { log(LogLevel::Error, "Proxy is enabled but proxy password is not present, " "if no password is required add an empty entry to configuration file"); return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration}; } } config.proxyPort = getInt("proxyPort").value_or(8080); return true; } private: std::pmr::memory_resource * memoryResource; std::pmr::unordered_map< std::pmr::string, std::pmr::string > keyValues{memoryResource}; }; class ConfigurationFactory { public: ConfigurationFactory() = default; std::optional< Configuration > systemConfig() { std::optional< Configuration > conf{Configuration{}}; ConfigurationReader reader{}; reader.loadFromFile("/etc/rublon.config"); if(auto ok = reader.applyTo(conf.value()); not ok.has_value()) { return std::nullopt; } return conf.value(); } }; } // namespace rublon