Bwi/logic fixes (#5)

* Code rafactoring, formating, add proper readout of operating system

* refactor
This commit is contained in:
rublon-bwi 2023-10-26 10:13:01 +02:00 committed by GitHub
parent aa06a78ffe
commit 25b29e6f32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 349 additions and 233 deletions

View File

@ -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

View File

@ -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

View File

@ -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<T, ConfigurationError> to(std::string_view);
tl::expected< T, ConfigurationError > to(std::string_view);
template <>
auto to(std::string_view arg) -> tl::expected<std::string, ConfigurationError> {
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<bool, ConfigurationError> {
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<int, ConfigurationError> {
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<std::string_view> 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<std::string_view>{
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;
}

View File

@ -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

View File

@ -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);

View File

@ -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<Response>, 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}};
}

View File

@ -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) {

View File

@ -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

View File

@ -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<uint32_t,conv::Error>{
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);

View File

@ -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;
}

View File

@ -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

View File

@ -8,15 +8,11 @@
#include <security/pam_misc.h>
#include <security/pam_modules.h>
#include <optional>
#include <string>
#include <system_error>
#include <type_traits>
#include <utility>
#include <memory>
#include "utils.hpp"
#include <rublon/non_owning_ptr.hpp>
#include <rublon/utils.hpp>
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);

View File

@ -1,3 +1,41 @@
#pragma once
namespace rublon {} // namespace rublon
#include <rublon/init.hpp>
#include <rublon/json.hpp>
#include <rublon/pam.hpp>
#include <rublon/rublon.hpp>
#include <rublon/utils.hpp>
namespace rublon {
// template<typename Pam_t>
// 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

View File

@ -5,6 +5,8 @@
#include <array>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
@ -22,6 +24,26 @@
#include <utility>
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<model> _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);

View File

@ -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<CoreHandlerError>()){
pam.print("\n RUBLON server returned '%s' exception",
error.get<CoreHandlerError>().reson.c_str());
}
pam.print("RUBLON authentication FAILED");
rublon::log(LogLevel::Warning, "User login failed");

View File

@ -9,84 +9,97 @@
#include <rublon/core_handler.hpp>
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<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 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{})));
}

View File

@ -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);
}

View File

@ -1,5 +1,4 @@
#include "gmock/gmock.h"
#include <cassert>
#include <gtest/gtest.h>
#include <memory_resource>
#include <string>
@ -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