diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt index 2f3d410..276aad4 100755 --- a/PAM/ssh/CMakeLists.txt +++ b/PAM/ssh/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/OTP.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/SMS.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/passcode_based_auth.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/non_owning_ptr.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam_action.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/rublon.hpp diff --git a/PAM/ssh/include/rublon/authentication_step_interface.hpp b/PAM/ssh/include/rublon/authentication_step_interface.hpp index 99d0398..69b60e6 100644 --- a/PAM/ssh/include/rublon/authentication_step_interface.hpp +++ b/PAM/ssh/include/rublon/authentication_step_interface.hpp @@ -39,20 +39,6 @@ class AuthenticationStep { auto & alloc = body.GetAllocator(); body.AddMember("tid", Value{this->_tid.c_str(), alloc}, alloc); } - - Error coreErrorHandler(const Error & coreResponse) const { - auto category = coreResponse.category(); - switch(category) { - case Error::k_SockerError: - log(LogLevel::Error, "Socker error, forcing override"); - return Error{PamBaypass{}}; - case Error::k_CoreHandlerError: - return Error{PamDeny{}}; - default: - Critical{}; - } - return Critical{}; - } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/configuration.hpp b/PAM/ssh/include/rublon/configuration.hpp index 569086e..e3b9032 100644 --- a/PAM/ssh/include/rublon/configuration.hpp +++ b/PAM/ssh/include/rublon/configuration.hpp @@ -19,7 +19,7 @@ class ConfigurationFactory; class ConfigurationError { public: - enum class Cause{NoDefaultValue}; + enum class Cause { NoDefaultValue }; const char * parameterName; const char * cause; }; @@ -44,66 +44,65 @@ namespace { T member_ptr_t(T C::*v); template < typename T > - tl::expected to(std::string_view); + tl::expected< T, ConfigurationError > to(std::string_view); template <> - auto to(std::string_view arg) -> tl::expected { + auto to(std::string_view arg) -> tl::expected< std::string, ConfigurationError > { return std::string{arg.data(), arg.size()}; } template <> - auto to(std::string_view arg) -> tl::expected { - return details::to_bool(arg); + auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > { + return conv::to_bool(arg); } template <> - auto to(std::string_view arg) -> tl::expected { - return details::to_uint32(arg).value_or(0); + auto to(std::string_view arg) -> tl::expected< int, ConfigurationError > { + return conv::to_uint32(arg).value_or(0); } } // namespace struct Entry { - enum class Source{UserInput,DefaultValue}; + 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{"",""}}; - }; + 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; + }; - /// 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"}}; - } - } + const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > { + params->*member = value; + return Source::UserInput; + }; - return to< pType >(userInput).and_then(setValue).or_else(returnBadInput); - }; + const auto returnBadInput = [&](const auto & /*error*/) -> tl::expected< Source, ConfigurationError > { + return tl::unexpected{ConfigurationError{"", ""}}; + }; + + 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(saveValue).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 > { + tl::expected< Source, ConfigurationError > ( + *_read)(const Entry * _this, Configuration::Parameters * params, std::string_view userInput); + + bool read(Configuration::Parameters * params, 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, @@ -113,7 +112,9 @@ struct Entry { }; const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > { - rublon::log(LogLevel::Error, "Configuration parameter '%s' is has no default value and is not provided in user configuraion, aborting", this->name); + rublon::log(LogLevel::Error, + "Configuration parameter '%s' is has no default value and is not provided in user configuraion, aborting", + this->name); return tl::unexpected{error}; }; @@ -126,7 +127,7 @@ constexpr auto make_entry(const char * name, const char * defaultValue) { return Entry{name, defaultValue, Entry::make_read_function< member >()}; } -using P = Configuration::Parameters; +using P = Configuration::Parameters; constexpr static inline std::array< Entry, 8 > configurationVariables = { // make_entry< &P::logging >("logging", "true"), make_entry< &P::systemToken >("systemToken", nullptr), @@ -153,11 +154,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{ + + 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}; @@ -174,8 +175,8 @@ class ConfigurationFactory { parameters[std::move(key)] = std::move(value); } - - for(const auto &entry : configurationVariables){ + + for(const auto & entry : configurationVariables) { if(not entry.read(&configValues, readParameterByName(entry.name))) return std::nullopt; } diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp index a42bc44..14825d0 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 > > { 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); + log(LogLevel::Error, "rublon core exception %s", exception); return handleCoreException(exception); } @@ -114,13 +114,12 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { Request request{&memoryResource}; Response response{&memoryResource}; - - stringifyTo(body, request.body); - + request.headers["Content-Type"] = pmrs("application/json"); request.headers["Accept"] = pmrs("application/json"); + stringifyTo(body, request.body); + signRequest(request); - std::pmr::string uri{url + path.data(), &memoryResource}; return http diff --git a/PAM/ssh/include/rublon/core_handler_interface.hpp b/PAM/ssh/include/rublon/core_handler_interface.hpp index cc4c360..b03797d 100644 --- a/PAM/ssh/include/rublon/core_handler_interface.hpp +++ b/PAM/ssh/include/rublon/core_handler_interface.hpp @@ -11,11 +11,6 @@ namespace rublon { template < typename Impl > class CoreHandlerInterface { public: - tl::expected< Document, Error > request(std::string_view path, const rublon::Document & body) const { - rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request"); - return static_cast< const Impl * >(this)->request(path, body); - } - tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const rublon::Document & body) const { rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request"); return static_cast< const Impl * >(this)->request(mr, path, body); diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp index ebd9994..6d345d5 100644 --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -65,8 +65,9 @@ class CURL { public: CURL() : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)} {} - - tl::expected< std::reference_wrapper, HttpError > request(std::string_view uri, const Request & request, Response&response) const { + + tl::expected< std::reference_wrapper< Response >, HttpError > + request(std::string_view uri, const Request & request, Response & response) const { memory::MonotonicStackResource< 8 * 1024 > stackResource; std::pmr::string response_data{&stackResource}; @@ -89,18 +90,18 @@ class CURL { curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); - log(LogLevel::Debug, "%s Request send, uri:%s body:\n%s\n", "CURL", uri.data(), request.body.c_str()); + log(LogLevel::Debug, "%s request send, uri:%s body:\n%s\n", "CURL", uri.data(), request.body.c_str()); const auto res = curl_easy_perform(curl.get()); if(res != CURLE_OK) { - log(LogLevel::Error, "%s No response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res)); + log(LogLevel::Error, "%s no response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res)); return tl::unexpected{HttpError{HttpError::Timeout, 0}}; } long http_code = 0; - curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, http_code); + curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code); - if(http_code >= 400) { + if(http_code >= 500) { log(LogLevel::Error, "%s response with code %d ", "CURL", http_code); return tl::unexpected{HttpError{HttpError::Error, http_code}}; } diff --git a/PAM/ssh/include/rublon/error.hpp b/PAM/ssh/include/rublon/error.hpp index 599d815..5e4c411 100644 --- a/PAM/ssh/include/rublon/error.hpp +++ b/PAM/ssh/include/rublon/error.hpp @@ -23,9 +23,9 @@ class HttpError { class CoreHandlerError { public: - enum ErrorClass { BadSigature, CoreException, BrokenData }; + enum ErrorClass { Unknown, BadSigature, CoreException, BrokenData }; - CoreHandlerError(ErrorClass e) : errorClass{e} {} + CoreHandlerError(ErrorClass e = Unknown) : errorClass{e} {} CoreHandlerError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {} ErrorClass errorClass; @@ -135,6 +135,11 @@ class Error { assert(isSameCategoryAs(e)); return errorClass() == Error{e}.errorClass(); } + + template < typename E > + constexpr const E & get() const { + return std::get< E >(_error); + } }; constexpr bool operator==(const Error & e, const HttpError & socket) { diff --git a/PAM/ssh/include/rublon/init.hpp b/PAM/ssh/include/rublon/init.hpp index 61cb64f..9480851 100644 --- a/PAM/ssh/include/rublon/init.hpp +++ b/PAM/ssh/include/rublon/init.hpp @@ -25,33 +25,33 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]}; } - tl::expected< MethodSelect_t, Error > handleInitError(const Error & error) const { - return tl::unexpected{this->coreErrorHandler(error)}; - } - template < typename PamInfo_t > void addPamInfo(Document & coreRequest, const PamInfo_t & pam) const { auto & alloc = coreRequest.GetAllocator(); coreRequest.AddMember("username", Value{pam.username().get(), alloc}, alloc); - // coreRequest.AddMember("userEmail", "bwi@rublon.com", alloc); /// TODO proper useremail - } - - static std::string osNameImpl() { - struct utsname uts; - uname(&uts); - return uts.sysname; } template < typename PamInfo_t > void addParams(Document & coreRequest, const PamInfo_t & pam) const { + memory::MonotonicStackResource< 256 > stackResource; + std::pmr::string osname{&stackResource}; auto & alloc = coreRequest.GetAllocator(); + const auto hasIssueFile = readFile("/etc/issue", osname); + const auto osnameTrimed = details::rtrim(hasIssueFile ? osname : "unknown"); + + if(not hasIssueFile) { + log(LogLevel::Warning, "No OS information available"); + } + + Value os{osnameTrimed.data(), static_cast< unsigned >(osnameTrimed.size()), alloc}; Value params{rapidjson::kObjectType}; + Value ip{pam.ip().get(), alloc}; - params.AddMember("userIP", Value{pam.ip().get(), alloc}, alloc); + params.AddMember("userIP", ip, alloc); params.AddMember("appVer", "v.0.0.1", alloc); /// TODO add version to cmake - params.AddMember("os", Value{osNameImpl().c_str(), alloc}, alloc); - + params.AddMember("os", os, alloc); + coreRequest.AddMember("params", std::move(params), alloc); } @@ -63,8 +63,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { /// TODO add core handler interface template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< MethodSelect_t, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { - const auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); }; - const auto handleInitError = [&](const auto & error) { return this->handleInitError(error); }; + const auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); }; RapidJSONPMRStackAlloc< 2048 > alloc{}; Document body{rapidjson::kObjectType, &alloc}; @@ -75,8 +74,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { return coreHandler .request(alloc, apiPath, body) // - .and_then(createMethod) - .or_else(handleInitError); + .and_then(createMethod); } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/method/method_select.hpp b/PAM/ssh/include/rublon/method/method_select.hpp index 1c95eb9..8995723 100644 --- a/PAM/ssh/include/rublon/method/method_select.hpp +++ b/PAM/ssh/include/rublon/method/method_select.hpp @@ -133,33 +133,42 @@ class MethodSelect { memory::StrictMonotonic_2k_HeapResource memoryResource; std::pmr::map< int, std::string > methods_id{&memoryResource}; pam.print("select method: "); - + + auto logMethodAvailable = [](auto &method){ + rublon::log(LogLevel::Debug, "Method %s found", method.c_str()); + }; + auto logMethodNotAvailable = [](auto &method){ + rublon::log(LogLevel::Debug, "Method %s found, but has no handler", method.c_str()); + }; + auto print_methods = [&]() { int i{}; for(const auto & method : _methods) { - rublon::log(LogLevel::Debug, "method %s found at pos %d", method.c_str(), i); if(method == "totp") { + logMethodAvailable(method); pam.print("%d: Mobile TOTP", i + 1); methods_id[++i] = "totp"; continue; } if(method == "sms") { + logMethodAvailable(method); pam.print("%d: SMS code", i + 1); methods_id[++i] = "sms"; continue; } + logMethodNotAvailable(method); } }; - const auto toMethodError = [&](details::ConversionError /*methodid*/) -> Error { + const auto toMethodError = [&](conv::Error /*methodid*/) -> Error { pam.print("Input is not an number, please correct"); return MethodError{MethodError::BadMethod}; }; const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, Error > { auto hasMethod = methods_id.find(methodid) != methods_id.end(); - pam.print("you selected: %d{%s}", methodid, hasMethod ? methods_id.at(methodid).c_str() : "unknown option"); + pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option"); if(!hasMethod) { return tl::unexpected{Error{Critical{}}}; /// TODO change to some more meaningfull error } else { @@ -171,16 +180,23 @@ class MethodSelect { const auto askForMethodAgain = [&](const Error &) { print_methods(); return pam - .scan(details::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) // + .scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) // .transform_error(toMethodError) .and_then(createMethod); /// TODO or_else(printErrorAndDenyAccess); ?? }; - + + const auto askForMethod = [&]() -> tl::expected{ + if(methods_id.size() == 1){ + pam.print("Only one method available"); + return 1; + } + return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()); + }; + print_methods(); - - return pam - .scan(details::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) // + + return askForMethod() // .transform_error(toMethodError) .and_then(createMethod) .or_else(askForMethodAgain); diff --git a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp index 12ee6e5..7fe2761 100644 --- a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp @@ -17,11 +17,11 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { return std::isdigit(static_cast< unsigned char >(ch)); } - static bool hasDigitsOnly(std::string_view userinput) { + static bool digitsOnly(std::string_view userinput) { return std::all_of(userinput.cbegin(), userinput.cend(), isdigit); } - static bool isProperLength(std::string_view userInput) { + static bool hasProperLength(std::string_view userInput) { return userInput.size() == 6; } @@ -30,7 +30,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { auto & alloc = body.GetAllocator(); auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage); - if(isProperLength(vericode) and hasDigitsOnly(vericode)) { + if(hasProperLength(vericode) and digitsOnly(vericode)) { body.AddMember("vericode", Value{vericode.c_str(), alloc}, alloc); return body; } diff --git a/PAM/ssh/include/rublon/non_owning_ptr.hpp b/PAM/ssh/include/rublon/non_owning_ptr.hpp new file mode 100644 index 0000000..ab9c229 --- /dev/null +++ b/PAM/ssh/include/rublon/non_owning_ptr.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace rublon { + +template < typename T > +class NonOwningPtr { + T * object; + + public: + constexpr NonOwningPtr(T * obj) : object{obj} {} + + constexpr T * get() noexcept { + assert(object != nullptr); + return object; + } + constexpr const T * get() const noexcept { + assert(object != nullptr); + return object; + } + constexpr operator const T *() const noexcept { + return get(); + } + constexpr operator T *() noexcept { + return get(); + } + constexpr T * operator->() { + return get(); + } + constexpr const T * operator->() const { + return get(); + } +}; + +} // namespace rublon diff --git a/PAM/ssh/include/rublon/pam.hpp b/PAM/ssh/include/rublon/pam.hpp index 5832353..ba57873 100644 --- a/PAM/ssh/include/rublon/pam.hpp +++ b/PAM/ssh/include/rublon/pam.hpp @@ -8,15 +8,11 @@ #include #include -#include -#include -#include #include #include -#include - -#include "utils.hpp" +#include +#include namespace rublon { class LinuxPam { @@ -47,7 +43,6 @@ class LinuxPam { template < typename... Ti > void print(const char * fmt, Ti... ti) const noexcept { - log(LogLevel::Debug, fmt, std::forward< Ti >(ti)...); pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti >(ti)...); sleep(1); diff --git a/PAM/ssh/include/rublon/rublon.hpp b/PAM/ssh/include/rublon/rublon.hpp index 53f687f..112f908 100644 --- a/PAM/ssh/include/rublon/rublon.hpp +++ b/PAM/ssh/include/rublon/rublon.hpp @@ -1,3 +1,41 @@ #pragma once -namespace rublon {} // namespace rublon +#include +#include +#include +#include +#include + +namespace rublon { + +// template +// static Configuration readConfig(const Pam_t&pam){ +// auto rublonConfig = ConfigurationFactory{}.systemConfig(); +// if(not rublonConfig.has_value()) { +// pam.print("\n"); +// pam.print("Rublon configuration does not exists or is invalid"); +// pam.print("\tcheck '%s' for more details\n", details::logPath()); +// } + + +// } + +template < typename Pam_T, typename CoreHandler_T> +class RublonBase { + Pam_T & pam; + Configuration config; + + public: + RublonBase(Pam_T & pam, Configuration config) : pam{pam}, config{config} {} + + + + int authenticate() { + std::byte sharedMemory[32 * 1024] = {}; + std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)}; + std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr}; + std::pmr::set_default_resource(&rublonPoolResource); + } +}; + +} // namespace rublon diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp index 02e760e..231e790 100644 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,6 +24,26 @@ #include namespace rublon { + +inline bool fileGood(const std::filesystem::path & path) { + std::ifstream file(path); + return file.good(); +} + +template < typename T > +inline bool readFile(const std::filesystem::path & path, T & destination) { + if(not fileGood(path)) + return false; + + std::ifstream file(path); + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + destination.resize(size); + file.seekg(0); + file.read(&destination[0], size); + return true; +} + namespace memory { struct holder { static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource(); @@ -106,16 +128,14 @@ constexpr LogLevel g_level = LogLevel::Debug; constexpr bool syncLogFile = true; namespace details { - - constexpr const char* logPath(){ + + constexpr const char * logPath() { constexpr auto path = "/var/log/rublon-ssh.log"; return path; } - - static void doLog(LogLevel level, const char * line) noexcept { - - static auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a"), fclose); + static void doLog(LogLevel level, const char * line) noexcept { + auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a"), fclose); if(fp) { fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[( int ) level], line); if(syncLogFile) @@ -134,63 +154,60 @@ template < typename... Ti > void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept { if(level < g_level) return; - - std::array< char, 1000 > line; - sprintf(line.data(), fmt, std::forward< Ti >(ti)...); + constexpr auto maxEntryLength = 1000; + std::array< char, maxEntryLength > line; + snprintf(line.data(), maxEntryLength, fmt, std::forward< Ti >(ti)...); details::doLog(level, line.data()); } -template < typename T > -class NonOwningPtr { - T * object; +class PrintUser{ public: - constexpr NonOwningPtr(T * obj) : object{obj} {} + template < typename Printer > + PrintUser(Printer & p) : _impl{std::make_unique< ModelImpl >(p)} {} - constexpr T * get() noexcept { - assert(object != nullptr); - return object; - } - constexpr const T * get() const noexcept { - assert(object != nullptr); - return object; - } - constexpr operator const T *() const noexcept { - return get(); - } - constexpr operator T *() noexcept { - return get(); - } - constexpr T * operator->() { - return get(); - } - constexpr const T * operator->() const { - return get(); - } + private: + struct model{ + virtual ~model(); + virtual void print(std::string_view line) const=0; + }; + + template < typename Printer_T > + struct ModelImpl : public model { + ModelImpl(Printer_T & printer) : _printer{printer} {} + void print(std::string_view line) const override { + _printer.print(line); + } + Printer_T & _printer; + }; + + std::unique_ptr _impl; }; -namespace details { - enum class ConversionError { OutOfRange, NotANumber }; - inline tl::expected< std::uint32_t, ConversionError > to_uint32(std::string_view userinput) noexcept { - try { - return std::stoi(userinput.data()); - } catch(const std::invalid_argument & e) { - return tl::make_unexpected(ConversionError::NotANumber); - } catch(const std::out_of_range & e) { - return tl::make_unexpected(ConversionError::OutOfRange); - } - } - +namespace conv{ inline bool to_bool(std::string_view value) { - /// TODO change to global allocator auto * buf = ( char * ) alloca(value.size() + 1); buf[value.size()] = '\0'; auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); }; - + std::transform(value.cbegin(), value.cend(), buf, asciitolower); return strcmp(buf, "true") == 0; } + + + enum class Error { OutOfRange, NotANumber }; + inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept { + try { + return std::stoi(userinput.data()); + } catch(const std::invalid_argument & e) { + return tl::make_unexpected(Error::NotANumber); + } catch(const std::out_of_range & e) { + return tl::make_unexpected(Error::OutOfRange); + } + } +} +namespace details { static inline std::string_view ltrim(std::string_view s) { while(s.length() && std::isspace(*s.begin())) s.remove_prefix(1); diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp index 05416ab..3580006 100644 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -72,6 +72,23 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu pam.print("\n RUBLON authentication bypased"); return PAM_SUCCESS; } + + if(error.is< HttpError >()){ + pam.print("\n RUBLON server unavalible"); + if(rublonConfig->parameters.bypass ){ + pam.print("\n RUBLON authentication bypased"); + return PAM_SUCCESS; + } + else{ + pam.print("RUBLON authentication FAILED"); + return PAM_MAXTRIES; + } + } + + if(error.is()){ + pam.print("\n RUBLON server returned '%s' exception", + error.get().reson.c_str()); + } pam.print("RUBLON authentication FAILED"); rublon::log(LogLevel::Warning, "User login failed"); diff --git a/PAM/ssh/tests/core_handler_tests.cpp b/PAM/ssh/tests/core_handler_tests.cpp index 1ad71cf..134c7cc 100644 --- a/PAM/ssh/tests/core_handler_tests.cpp +++ b/PAM/ssh/tests/core_handler_tests.cpp @@ -9,84 +9,97 @@ #include using namespace rublon; -class CoreHandlerTestable : public CoreHandler< HttpHandlerMock > { - public: - CoreHandlerTestable(rublon::Configuration _conf = conf) : CoreHandler< HttpHandlerMock >{_conf} {} - auto & _http() { - return http; - } - - tl::expected< Document, Error > request(std::string_view path, const Document & body){ - static auto mr = std::pmr::get_default_resource(); - static RapidJSONPMRAlloc alloc{mr}; - return CoreHandler< HttpHandlerMock >::request(alloc, path, body); - } - +class CoreHandlerTestable : public CoreHandler { +public: + CoreHandlerTestable(rublon::Configuration _conf = conf) + : CoreHandler{_conf} {} + auto &_http() { return http; } + + tl::expected request(std::string_view path, + const Document &body) { + static auto mr = std::pmr::get_default_resource(); + static RapidJSONPMRAlloc alloc{mr}; + return CoreHandler::request(alloc, path, body); + } }; class CoreHandlerTests : public testing::Test { - public: - CoreHandlerTests() : http{sut._http()} { - doc.SetObject(); - } +public: + CoreHandlerTests() : http{sut._http()} { doc.SetObject(); } - CoreHandlerTestable sut; - HttpHandlerMock & http; - rublon::Response _res{std::pmr::get_default_resource()}; + CoreHandlerTestable sut; + HttpHandlerMock &http; + rublon::Response _res{std::pmr::get_default_resource()}; - Document doc; + Document doc; }; using namespace testing; TEST_F(CoreHandlerTests, coreShouldSetConnectionErrorOnBrokenConnection) { - EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.withTimeoutError())); - EXPECT_THAT(sut.request("", doc), // - AllOf(Not(HasValue()), Unexpected(HttpError{}))); + EXPECT_CALL(http, request(_, _)) + .WillOnce(Return(RawHttpResponse{_res}.withTimeoutError())); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(HttpError{}))); } -TEST_F(CoreHandlerTests, coreShouldCheckSignatureAndReturnBadSignatureBeforeAnythingElse) { - EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.initMessage().withBrokenBody().withBrokenSignature())); - EXPECT_THAT(sut.request("", doc), // - AllOf(Not(HasValue()), Unexpected(CoreHandlerError{CoreHandlerError::BadSigature}))); +TEST_F(CoreHandlerTests, + coreShouldCheckSignatureAndReturnBadSignatureBeforeAnythingElse) { + EXPECT_CALL(http, request(_, _)) + .WillOnce(Return(RawHttpResponse{_res} + .initMessage() + .withBrokenBody() + .withBrokenSignature())); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(CoreHandlerError{ + CoreHandlerError::BadSigature}))); } TEST_F(CoreHandlerTests, coreShouldSetBrokenDataWhenResultIsNotAvailable) { - EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.initMessage().withBrokenBody())); - EXPECT_THAT(sut.request("", doc), // - AllOf(Not(HasValue()), Unexpected(CoreHandlerError{CoreHandlerError::BrokenData}))); + EXPECT_CALL(http, request(_, _)) + .WillOnce(Return(RawHttpResponse{_res}.initMessage().withBrokenBody())); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(CoreHandlerError{ + CoreHandlerError::BrokenData}))); } TEST_F(CoreHandlerTests, coreSignatureIsBeingChecked) { - EXPECT_CALL(http, request(Eq(conf.parameters.apiServer + "/path"), _)).WillOnce(Return(RawHttpResponse{_res}.initMessage())); - EXPECT_THAT(sut.request("/path", doc), // - AllOf(HasValue(), IsObject(), HasKey("/result/tid"))); + EXPECT_CALL(http, request(Eq(conf.parameters.apiServer + "/path"), _)) + .WillOnce(Return(RawHttpResponse{_res}.initMessage())); + EXPECT_THAT(sut.request("/path", doc), // + AllOf(HasValue(), IsObject(), HasKey("/result/tid"))); } TEST_F(CoreHandlerTests, onHttpProblemsAccessShouldBeDeniedByDefault) { - EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.initMessage().withServiceUnavailableError())); - EXPECT_THAT(sut.request("/path", doc), // - AllOf(Not(HasValue()), Unexpected(PamDeny{}))); + EXPECT_CALL(http, request(_, _)) + .WillOnce(Return( + RawHttpResponse{_res}.initMessage().withServiceUnavailableError())); + EXPECT_THAT(sut.request("/path", doc), // + 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())); +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()} { - } +public: + CoreHandlerWithBypassTests() : sut{confBypass}, http{sut._http()} {} - CoreHandlerTestable sut; - rublon::Response _res{memory::default_resource()}; - HttpHandlerMock & http; + CoreHandlerTestable sut; + rublon::Response _res{memory::default_resource()}; + HttpHandlerMock &http; }; -TEST_F(CoreHandlerWithBypassTests, onHttpProblemsAccessShouldBeBypassedWhenEnabled) { - EXPECT_CALL(http, request(_, _)).WillOnce(Return(RawHttpResponse{_res}.initMessage().withServiceUnavailableError())); - EXPECT_THAT(sut.request("/path", Document{}), // - AllOf(Not(HasValue()), Unexpected(PamBaypass{}))); +TEST_F(CoreHandlerWithBypassTests, + onHttpProblemsAccessShouldBeBypassedWhenEnabled) { + EXPECT_CALL(http, request(_, _)) + .WillOnce(Return( + RawHttpResponse{_res}.initMessage().withServiceUnavailableError())); + EXPECT_THAT(sut.request("/path", Document{}), // + AllOf(Not(HasValue()), Unexpected(PamBaypass{}))); } diff --git a/PAM/ssh/tests/init_test.cpp b/PAM/ssh/tests/init_test.cpp index 426037f..ff9591c 100644 --- a/PAM/ssh/tests/init_test.cpp +++ b/PAM/ssh/tests/init_test.cpp @@ -42,16 +42,18 @@ TEST_F(RublonHttpInitTest, initializationSendsRequestOnGoodPath) { sut.handle(coreHandler, pam); } -TEST_F(RublonHttpInitTest, rublon_Accept_pamLoginWhenThereIsNoConnection) { - EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(RawCoreResponse{}.withTimeoutError())); - EXPECT_THAT(sut.handle(coreHandler, pam), // - AllOf(Not(HasValue()), IsPAMBaypass())); -} +/// TODO this is app level logic, as core errors are hanfled in pam.cpp +// TEST_F(RublonHttpInitTest, rublon_Accept_pamLoginWhenThereIsNoConnection) { +// EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(RawCoreResponse{}.withTimeoutError())); +// EXPECT_THAT(sut.handle(coreHandler, pam), // +// AllOf(Not(HasValue()), IsPAMBaypass())); +// } - TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerHasBadSignature) { - EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(RawCoreResponse{}.withBasSignature())); - EXPECT_THAT(sut.handle(coreHandler, pam), IsPAMDeny()); - } +/// TODO this is app level logic, as core errors are hanfled in pam.cpp +// TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerHasBadSignature) { +// EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(RawCoreResponse{}.withBasSignature())); +// EXPECT_THAT(sut.handle(coreHandler, pam), IsPAMDeny()); +// } // TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerReturnsBrokenData) { // EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}})); @@ -63,9 +65,8 @@ TEST_F(RublonHttpInitTest, rublon_Accept_pamLoginWhenThereIsNoConnection) { // EXPECT_THAT(sut.handle(coreHandler, pam), HoldsPamAction(PamAction::decline)); // } - TEST_F(RublonHttpInitTest, AllNeededInformationNeedsToBePassedToMethodFactory) { - EXPECT_CALL(coreHandler, request(_, _)) - .WillOnce(Return( RawCoreResponse{}.initMessage().withMethods("sms", "otp"))); - // EXPECT_CALL(methodFactoryMock, create_mocked(_,"transaction ID") ); - sut.handle(coreHandler, pam); - } +TEST_F(RublonHttpInitTest, AllNeededInformationNeedsToBePassedToMethodFactory) { + EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(RawCoreResponse{}.initMessage().withMethods("sms", "otp"))); + // EXPECT_CALL(methodFactoryMock, create_mocked(_,"transaction ID") ); + sut.handle(coreHandler, pam); +} diff --git a/PAM/ssh/tests/utils_tests.cpp b/PAM/ssh/tests/utils_tests.cpp index 9d04593..7341c99 100644 --- a/PAM/ssh/tests/utils_tests.cpp +++ b/PAM/ssh/tests/utils_tests.cpp @@ -1,5 +1,4 @@ #include "gmock/gmock.h" -#include #include #include #include @@ -10,11 +9,11 @@ using namespace rublon; using namespace testing; TEST(Utils, toBoolReturnsTrueWhenStringIsPassed) { - EXPECT_TRUE(details::to_bool("TrUe")); + EXPECT_TRUE(conv::to_bool("TrUe")); } TEST(Utils, toBoolReturnsFalseWhenStringIsPassed) { - EXPECT_FALSE(details::to_bool("False")); + EXPECT_FALSE(conv::to_bool("False")); } std::string response = R"http(HTTP/2 200