305 lines
12 KiB
C++
305 lines
12 KiB
C++
#pragma once
|
|
|
|
#include "tl/expected.hpp"
|
|
#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) : memoryResource{mr} {}
|
|
|
|
// change to StaticString
|
|
std::pmr::string systemToken{memoryResource};
|
|
std::pmr::string secretKey{memoryResource};
|
|
std::pmr::string apiServer{memoryResource};
|
|
|
|
int prompt{};
|
|
bool enablePasswdEmail{}; // obsolete
|
|
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, std::string_view filepath) : memoryResource(memResource) {
|
|
keyValues.reserve(20);
|
|
loadFromFile(filepath);
|
|
}
|
|
|
|
// Load config from file path
|
|
void loadFromFile(std::string_view filepath) {
|
|
using namespace memory::literals;
|
|
memory::MonotonicStack_1k_Resource memoryResource;
|
|
std::ifstream file(filepath.data());
|
|
if(not file.good())
|
|
return;
|
|
|
|
std::pmr::string line{&memoryResource};
|
|
line.reserve(300);
|
|
|
|
while(std::getline(file, line)) {
|
|
details::trimInPlace(line);
|
|
if(!line.length())
|
|
continue;
|
|
|
|
if(line[0] == '#' || line[0] == ';')
|
|
continue;
|
|
|
|
auto posEqual = line.find('=');
|
|
std::pmr::string key{line.substr(0, posEqual), &memoryResource};
|
|
std::pmr::string value{line.substr(posEqual + 1), &memoryResource};
|
|
|
|
keyValues[std::move(key)] = std::move(value);
|
|
}
|
|
}
|
|
|
|
// Load values into Configuration object, with defaults provided
|
|
tl::expected< bool, ConfigurationError > applyTo(Configuration & config) const {
|
|
using string = std::pmr::string;
|
|
|
|
auto logRequiredFieldNotAvailable = [](auto fieldname) { log(LogLevel::Error, "%s field is not set", fieldname); };
|
|
|
|
// 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;
|
|
};
|
|
|
|
auto parseProxyURL = [&](const char * envValue) -> bool {
|
|
// Very simple parser: scheme://[user[:pass]@]host[:port]
|
|
std::string_view url = envValue;
|
|
|
|
std::string_view scheme{};
|
|
std::string_view auth{};
|
|
std::string_view hostport{};
|
|
|
|
auto scheme_pos = url.find("://");
|
|
if(scheme_pos != std::string_view::npos) {
|
|
scheme = url.substr(0, scheme_pos);
|
|
url = url.substr(scheme_pos + 3);
|
|
} else {
|
|
scheme = "http"; // default
|
|
}
|
|
|
|
auto at_pos = url.find('@');
|
|
if(at_pos != std::string_view::npos) {
|
|
auth = url.substr(0, at_pos);
|
|
hostport = url.substr(at_pos + 1);
|
|
} else {
|
|
hostport = url;
|
|
}
|
|
|
|
std::string_view host = hostport;
|
|
std::string_view port_str{};
|
|
auto colon_pos = hostport.rfind(':');
|
|
if(colon_pos != std::string_view::npos && colon_pos < hostport.size() - 1) {
|
|
host = hostport.substr(0, colon_pos);
|
|
port_str = hostport.substr(colon_pos + 1);
|
|
}
|
|
|
|
config.proxyEnabled = true;
|
|
config.proxyType = scheme;
|
|
config.proxyServer = host;
|
|
|
|
if(!port_str.empty()) {
|
|
config.proxyPort = conv::to_uint32opt(port_str);
|
|
if(not config.proxyPort) {
|
|
log(LogLevel::Error, "Invalid proxy port in environment variable");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!auth.empty()) {
|
|
auto colon = auth.find(':');
|
|
if(colon != std::string_view::npos) {
|
|
config.proxyUsername = auth.substr(0, colon);
|
|
config.proxyPass = auth.substr(colon + 1);
|
|
} else {
|
|
config.proxyUsername = auth;
|
|
}
|
|
config.proxyAuthRequired = true;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/// 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()) {
|
|
logRequiredFieldNotAvailable("systemToken");
|
|
return tl::unexpected{val.error()};
|
|
} else {
|
|
config.systemToken = std::move(val.value());
|
|
}
|
|
|
|
if(auto val = getStringReq("secretKey"); not val.has_value()) {
|
|
logRequiredFieldNotAvailable("secretKey");
|
|
return tl::unexpected{val.error()};
|
|
} else {
|
|
config.secretKey = std::move(val.value());
|
|
}
|
|
|
|
if(auto val = getStringReq("rublonApiServer"); not val.has_value()) {
|
|
logRequiredFieldNotAvailable("rublonApiServer");
|
|
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");
|
|
|
|
// Apply fallback if no config is set
|
|
if(config.proxyEnabled && (!config.proxyType || config.proxyType->empty()) &&
|
|
(!config.proxyServer || config.proxyServer->empty())) {
|
|
log(LogLevel::Info, "Proxy is enabled but no configuration for it is provided, trying to read from env");
|
|
if(const char * https_proxy = std::getenv("https_proxy"); https_proxy && *https_proxy) {
|
|
if(parseProxyURL(https_proxy)) {
|
|
log(LogLevel::Info, "Loaded proxy config from HTTPS_PROXY");
|
|
}
|
|
} else if(const char * http_proxy = std::getenv("http_proxy"); http_proxy && *http_proxy) {
|
|
if(parseProxyURL(http_proxy)) {
|
|
log(LogLevel::Info, "Loaded proxy config from HTTP_PROXY");
|
|
}
|
|
}
|
|
}
|
|
|
|
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};
|
|
};
|
|
|
|
} // namespace rublon
|