238 lines
8.8 KiB
C++
238 lines
8.8 KiB
C++
#pragma once
|
|
|
|
#include <rublon/error.hpp>
|
|
#include <rublon/memory.hpp>
|
|
#include <rublon/static_string.hpp>
|
|
#include <rublon/utils.hpp>
|
|
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include <memory_resource>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
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<string> 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
|