Bwi/logic fixes (#5)
* Code rafactoring, formating, add proper readout of operating system * refactor
This commit is contained in:
parent
aa06a78ffe
commit
25b29e6f32
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}};
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
34
PAM/ssh/include/rublon/non_owning_ptr.hpp
Normal file
34
PAM/ssh/include/rublon/non_owning_ptr.hpp
Normal 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
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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{})));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user