* Add phone call authentication method * Remove dynamic mem allocation from error handler * Add more error handling code * Move error handling to different file * Remove Socket IO dependency * cleanup in websocket code * Add rapidjson as cmake dependency * Added Dockerfiles as primary build system for packages * Changed policy in CMakeList to work with lower version of CMake * Fix opensuse builds * Link filesystem library in gcc 8.5 or older
197 lines
7.1 KiB
C++
197 lines
7.1 KiB
C++
#pragma once
|
|
|
|
#include <rublon/error.hpp>
|
|
#include <rublon/static_string.hpp>
|
|
#include <rublon/utils.hpp>
|
|
#include <type_traits>
|
|
|
|
template < typename T >
|
|
constexpr bool is_static_string_v = std::is_base_of_v< rublon::details::StaticStringBase, T >;
|
|
|
|
static_assert(is_static_string_v< rublon::StaticString< 32 > >);
|
|
|
|
namespace rublon {
|
|
class ConfigurationFactory;
|
|
|
|
enum class FailMode { bypass, deny };
|
|
|
|
class Configuration {
|
|
public:
|
|
// change to StaticString
|
|
StaticString< 32 > systemToken{};
|
|
StaticString< 32 > secretKey{};
|
|
StaticString< 4096 > apiServer{};
|
|
|
|
int prompt{};
|
|
bool enablePasswdEmail{};
|
|
bool logging{};
|
|
bool autopushPrompt{};
|
|
FailMode failMode{};
|
|
};
|
|
|
|
namespace {
|
|
template < class C, typename T >
|
|
T member_ptr_t(T C::*v);
|
|
|
|
template < typename T >
|
|
tl::expected< T, ConfigurationError > to(std::string_view);
|
|
|
|
template < class T >
|
|
auto to_string(std::string_view arg) -> tl::expected< T, ConfigurationError > {
|
|
T value{};
|
|
assert(arg.size() <= (value.size() - 1));
|
|
std::memcpy(value.data(), arg.data(), arg.size());
|
|
return value;
|
|
}
|
|
|
|
template <>
|
|
auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > {
|
|
return conv::to_bool(arg);
|
|
}
|
|
template <>
|
|
auto to(std::string_view arg) -> tl::expected< int, ConfigurationError > {
|
|
return conv::to_uint32(arg).value_or(0);
|
|
}
|
|
|
|
template <>
|
|
auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > {
|
|
if(arg == "bypass")
|
|
return FailMode::bypass;
|
|
if(arg == "deny")
|
|
return FailMode::deny;
|
|
return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}};
|
|
}
|
|
|
|
template < typename T >
|
|
auto parse(std::string_view arg) -> tl::expected< T, ConfigurationError > {
|
|
if(arg.empty()) {
|
|
return tl::unexpected{ConfigurationError::ErrorClass::Empty};
|
|
} else {
|
|
if constexpr(is_static_string_v< T >) {
|
|
return to_string< T >(arg);
|
|
} else {
|
|
return to< T >(arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
struct Entry {
|
|
enum class Source { UserInput, DefaultValue };
|
|
template < auto member >
|
|
static constexpr auto make_read_function() {
|
|
using pType = decltype(member_ptr_t(member));
|
|
|
|
return
|
|
[](const Entry * _this, Configuration * configuration, std::string_view userInput) -> tl::expected< Source, ConfigurationError > {
|
|
const auto setDefaultValue = [&](const ConfigurationError & error) -> tl::expected< Source, ConfigurationError > {
|
|
log(LogLevel::Warning, "applying user provided value for %s parameter, faild with %s", _this->name, error.what());
|
|
if(_this->defaultValue != nullptr) {
|
|
configuration->*member = parse< pType >(_this->defaultValue).value();
|
|
return Source::DefaultValue;
|
|
} else {
|
|
log(LogLevel::Error, "parameter %s has not been found and has no default value", _this->name);
|
|
if(userInput.empty())
|
|
return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound};
|
|
else
|
|
return tl::unexpected{ConfigurationError::ErrorClass::BadInput};
|
|
}
|
|
};
|
|
|
|
const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
|
|
configuration->*member = value;
|
|
return Source::UserInput;
|
|
};
|
|
|
|
return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue);
|
|
};
|
|
}
|
|
|
|
const char * name;
|
|
const char * defaultValue;
|
|
tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration * configuration, std::string_view userInput);
|
|
|
|
bool read(Configuration * configuration, std::optional< std::string_view > userInput) const {
|
|
constexpr const auto emptyString = "";
|
|
const auto logStored = [&](const auto & source) -> tl::expected< Source, ConfigurationError > {
|
|
rublon::log(LogLevel::Debug,
|
|
"Configuration parameter '%s' was set to '%s'%s",
|
|
this->name,
|
|
this->defaultValue,
|
|
source == Source::DefaultValue ? " (default)" : "");
|
|
return source;
|
|
};
|
|
|
|
const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > {
|
|
rublon::log(LogLevel::Error,
|
|
"Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting",
|
|
this->name);
|
|
return tl::unexpected{error};
|
|
};
|
|
|
|
return _read(this, configuration, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value();
|
|
}
|
|
};
|
|
|
|
template < auto member >
|
|
constexpr auto make_entry(const char * name, const char * defaultValue) {
|
|
return Entry{name, defaultValue, Entry::make_read_function< member >()};
|
|
}
|
|
|
|
constexpr static inline std::array< Entry, 8 > configurationVariables = { //
|
|
make_entry< &Configuration::logging >("logging", "true"),
|
|
make_entry< &Configuration::systemToken >("systemToken", nullptr),
|
|
make_entry< &Configuration::secretKey >("secretKey", nullptr),
|
|
make_entry< &Configuration::apiServer >("rublonApiServer", nullptr),
|
|
make_entry< &Configuration::prompt >("prompt", "1"),
|
|
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
|
|
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
|
|
make_entry< &Configuration::failMode >("failMode", "deny")};
|
|
|
|
class ConfigurationFactory {
|
|
public:
|
|
ConfigurationFactory() = default;
|
|
|
|
std::optional< Configuration > systemConfig() {
|
|
memory::MonotonicStackResource< 8 * 1024 > stackResource;
|
|
Configuration configuration{};
|
|
|
|
std::ifstream file(std::filesystem::path{"/etc/rublon.config"});
|
|
if(not file.good())
|
|
return std::nullopt;
|
|
|
|
std::pmr::string line{&stackResource};
|
|
line.reserve(100);
|
|
std::pmr::map< std::pmr::string, std::pmr::string > parameters{&stackResource};
|
|
|
|
const auto readParameterByName = [&](std::string_view name) -> std::optional< std::string_view > {
|
|
return parameters.count(name.data()) ? std::optional< std::string_view >{parameters.at(name.data())} : std::nullopt;
|
|
};
|
|
|
|
while(std::getline(file, line)) {
|
|
std::pmr::string key{&stackResource};
|
|
std::pmr::string value{&stackResource};
|
|
|
|
if(!line.length())
|
|
continue;
|
|
|
|
if(line[0] == '#' || line[0] == ';')
|
|
continue;
|
|
|
|
auto posEqual = line.find('=');
|
|
key = line.substr(0, posEqual);
|
|
value = line.substr(posEqual + 1);
|
|
|
|
parameters[std::move(key)] = std::move(value);
|
|
}
|
|
|
|
for(const auto & entry : configurationVariables) {
|
|
if(not entry.read(&configuration, readParameterByName(entry.name)))
|
|
return std::nullopt;
|
|
}
|
|
|
|
return configuration;
|
|
}
|
|
};
|
|
} // namespace rublon
|