From db418ef01ddf02825ec9bd7a62c5c434e4675b0e Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Wed, 11 Oct 2023 12:23:55 +0200 Subject: [PATCH] Fix access to unknown object in json --- PAM/ssh/include/rublon/configuration.hpp | 158 +++++++++++++++++------ PAM/ssh/include/rublon/core_handler.hpp | 2 +- PAM/ssh/lib/pam.cpp | 2 +- PAM/ssh/tests/core_handler_mock.hpp | 2 +- PAM/ssh/tests/core_handler_tests.cpp | 6 + PAM/ssh/tests/http_mock.hpp | 16 ++- 6 files changed, 143 insertions(+), 43 deletions(-) diff --git a/PAM/ssh/include/rublon/configuration.hpp b/PAM/ssh/include/rublon/configuration.hpp index f069235..2769bac 100644 --- a/PAM/ssh/include/rublon/configuration.hpp +++ b/PAM/ssh/include/rublon/configuration.hpp @@ -16,6 +16,14 @@ namespace rublon { class ConfigurationFactory; + +class ConfigurationError { + public: + enum class Cause{NoDefaultValue}; + const char * parameterName; + const char * cause; +}; + class Configuration { public: struct Parameters { @@ -27,10 +35,112 @@ class Configuration { bool logging; bool autopushPrompt; bool bypass; - bool offlineBypass; + bool offlineBypas; } parameters; }; +namespace { + template < class C, typename T > + T member_ptr_t(T C::*v); + + template < typename T > + tl::expected to(std::string_view); + + template <> + auto to(std::string_view arg) -> tl::expected { + return std::string{arg.data(), arg.size()}; + } + template <> + auto to(std::string_view arg) -> tl::expected { + return details::to_bool(arg); + } + template <> + auto to(std::string_view arg) -> tl::expected { + return details::to_uint32(arg).value_or(0); + } + +} // 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::Parameters * params, + std::string_view userInput) -> tl::expected< Source, ConfigurationError > { + const auto setDefaultValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError >{ + params->*member = value; + return Source::DefaultValue; + }; + + const auto setValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError >{ + params->*member = value; + return Source::UserInput; + }; + + const auto returnBadInput = [&](const auto &/*error*/) -> tl::expected< Source, ConfigurationError >{ + return tl::unexpected{ConfigurationError{"",""}}; + }; + + /// TODO error handling + if(userInput.empty()) { + if(_this->defaultValue != nullptr) { + return to< pType >(_this->defaultValue).and_then(setDefaultValue).or_else(returnBadInput); + } else { + return tl::unexpected{ConfigurationError{_this->name, "No default value"}}; + } + } + + return to< pType >(userInput).and_then(setValue).or_else(returnBadInput); + }; + } + + const char * name; + const char * defaultValue; + tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration::Parameters * params, std::string_view userInput); + + bool read(Configuration::Parameters * params, std::optional userInput) const{ + constexpr const auto emptyString = ""; + const auto logStored = [&](const auto & source) -> tl::expected< Source, ConfigurationError > { + if(source == Source::DefaultValue) { + rublon::log(LogLevel::Debug, "Parameter %s was set to {%s}, using default value", this->name, this->defaultValue); + } else { + rublon::log(LogLevel::Debug, "Parameter %s was set to {%s}", this->name, userInput.value().data()); + } + return source; + }; + + const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > { + rublon::log(LogLevel::Error, "Parameter %s is unavailable, aborting", this->name); + return tl::unexpected{error}; + }; + + return _read(this, params, userInput.value_or(emptyString)) + .and_then(logStored) + .or_else(logError) + .map([](const auto &) { return true; }) + .map_error([](const auto &) { return false; }).value(); + } +}; + +template < auto member > +constexpr auto make_entry(const char * name, const char * defaultValue) { + return Entry{name, defaultValue, Entry::make_read_function< member >()}; +} + +using P = Configuration::Parameters; +constexpr static inline std::array< Entry, 8 > configurationVariables = { // + make_entry< &P::logging >("logging", "true"), + make_entry< &P::systemToken >("systemToken", nullptr), + make_entry< &P::secretKey >("secretKey", nullptr), + make_entry< &P::apiServer >("rublonApiServer", nullptr), + make_entry< &P::prompt >("prompt", "1"), + make_entry< &P::enablePasswdEmail >("enablePasswdEmail", "true"), + make_entry< &P::autopushPrompt >("autopushPrompt", "false"), + make_entry< &P::offlineBypas >("failMode", "bypas")}; + class ConfigurationFactory { public: ConfigurationFactory() = default; @@ -47,7 +157,11 @@ class ConfigurationFactory { 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{ + 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}; @@ -64,44 +178,10 @@ class ConfigurationFactory { parameters[std::move(key)] = std::move(value); } - - auto saveStr = [&](auto member) { // - return [member](Params * params, std::string_view value) { params->*member = value; }; - }; - - auto saveInt = [](auto member) { // - return [member](Params * params, std::string_view value) { params->*member = std::stoi(value.data()); }; - }; - - auto saveBool = [](auto member) { - return [member](Params * params, std::string_view value) { params->*member = details::to_bool(value); }; - }; - auto saveBypass = [](auto member) { - return [member](Params * params, std::string_view value) { params->*member = (value == "bypas"); }; - }; - - std::tuple< const char *, std::function< void(Params *, const char *) >, const char * > checks[]{// - {"logging", saveBool(&Params::logging), "true"}, - {"systemToken", saveStr(&Params::systemToken), nullptr}, - {"secretKey", saveStr(&Params::secretKey), nullptr}, - {"rublonApiServer", saveStr(&Params::apiServer), nullptr}, - {"prompt", saveInt(&Params::prompt), "1"}, - {"enablePasswdEmail", saveBool(&Params::enablePasswdEmail), "true"}, - {"autopushPrompt", saveBool(&Params::autopushPrompt), "false"}, - {"failMode", saveBypass(&Params::bypass), "bypas"}/*, - {"offlineBypass", saveBool(&Params::bypass), "true"}*/}; - - for(const auto & [key, set_func, default_value] : checks) { - if(auto it = parameters.find(key); it != parameters.end()) { - set_func(&configValues, it->second.data()); - } else { - if(default_value) { - /// TODO exit?? - } else { // not required - set_func(&configValues, default_value); - } - } + for(const auto &entry : configurationVariables){ + if(not entry.read(&configValues, readParameterByName(entry.name))) + return std::nullopt; } return Configuration{std::move(configValues)}; diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp index c1b46c5..6c67c71 100644 --- a/PAM/ssh/include/rublon/core_handler.hpp +++ b/PAM/ssh/include/rublon/core_handler.hpp @@ -60,7 +60,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}}; } - if(resp["result"].HasMember("exception")) { + if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("exception")) { const auto & exception = resp["result"]["exception"].GetString(); log(LogLevel::Error, "rublon Core exception %s", exception); return handleCoreException(exception); diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp index 27f6a47..419fa5a 100644 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -36,7 +36,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu auto rublonConfig = ConfigurationFactory{}.systemConfig(); if(not rublonConfig.has_value()) { - pam.print("Rublon configuration not exists"); + pam.print("Rublon configuration not exists or is invalid (check logs)"); pam.print("Please create /etc/rublon.config file"); return PAM_SUCCESS; } diff --git a/PAM/ssh/tests/core_handler_mock.hpp b/PAM/ssh/tests/core_handler_mock.hpp index 5c7af53..7e77685 100644 --- a/PAM/ssh/tests/core_handler_mock.hpp +++ b/PAM/ssh/tests/core_handler_mock.hpp @@ -10,7 +10,7 @@ class CoreHandlerMock : public rublon::CoreHandlerInterface< CoreHandlerMock > { public: tl::expected< rublon::Document, rublon::Error > - request(rublon::RapidJSONPMRAlloc &alloc, std::string_view path, const rublon::Document & req) const { + request(rublon::RapidJSONPMRAlloc &, std::string_view path, const rublon::Document & req) const { // const rublon::Document &doc = request(path, req).value(); // rublon::Document ret{&alloc}; // ret.CopyFrom(doc, alloc); diff --git a/PAM/ssh/tests/core_handler_tests.cpp b/PAM/ssh/tests/core_handler_tests.cpp index 6a36b1c..1ad71cf 100644 --- a/PAM/ssh/tests/core_handler_tests.cpp +++ b/PAM/ssh/tests/core_handler_tests.cpp @@ -69,6 +69,12 @@ TEST_F(CoreHandlerTests, onHttpProblemsAccessShouldBeDeniedByDefault) { AllOf(Not(HasValue()), Unexpected(PamDeny{}))); } +TEST_F(CoreHandlerTests, onSuccessfullMessageCoreShouldByFine){ + EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.authenticationSuccessfullMessage())); + EXPECT_THAT(sut.request("/path", doc), // + AllOf(HasValue())); +} + class CoreHandlerWithBypassTests : public testing::Test { public: CoreHandlerWithBypassTests() : sut{confBypass}, http{sut._http()} { diff --git a/PAM/ssh/tests/http_mock.hpp b/PAM/ssh/tests/http_mock.hpp index b640fd7..06ae5ba 100644 --- a/PAM/ssh/tests/http_mock.hpp +++ b/PAM/ssh/tests/http_mock.hpp @@ -133,6 +133,15 @@ class CodeVerificationResponse { } }; +class AuthenticationOkResponse { + static constexpr const char * authOk = R"json({"status":"OK","result":true})json"; + + public: + static std::string generate(const ResponseMockOptions &) { + return authOk; + } +}; + template < typename Generator > class ResponseBase { public: @@ -151,6 +160,11 @@ class ResponseBase { return static_cast< Generator & >(*this); } + Generator & authenticationSuccessfullMessage() { + _coreGenerator = AuthenticationOkResponse{}; + return static_cast< Generator & >(*this); + } + Generator & withBrokenBody() { options.generateBrokenBody = true; return static_cast< Generator & >(*this); @@ -205,7 +219,7 @@ class ResponseBase { return rublon::Error{options.error}; } - std::variant< InitCoreResponse, MethodSelectCoreResponse, CodeVerificationResponse > _coreGenerator; + std::variant< InitCoreResponse, MethodSelectCoreResponse, CodeVerificationResponse, AuthenticationOkResponse > _coreGenerator; ResponseMockOptions options; };