ADD postMethod

This commit is contained in:
Bartosz Wieczorek 2023-07-26 16:27:03 +02:00
parent e9f612753b
commit 843574499f
14 changed files with 218 additions and 93 deletions

View File

@ -5,23 +5,26 @@ add_library(rublon-ssh
INTERFACE
)
add_library(rublon-ssh_ide INTERFACE
./include/rublon/authentication_step_interface.hpp
./include/rublon/configuration.hpp
./include/rublon/core_handler.hpp
./include/rublon/core_handler_interface.hpp
./include/rublon/curl.hpp
./include/rublon/init.hpp
./include/rublon/json.hpp
./include/rublon/method/method_factory.hpp
./include/rublon/method/OTP.hpp
./include/rublon/method/SMS.hpp
./include/rublon/pam_action.hpp
./include/rublon/pam.hpp
./include/rublon/rublon.hpp
./include/rublon/sign.hpp
./include/rublon/span.hpp
./include/rublon/utils.hpp)
if(${CMAKE_VERSION} VERSION_GREATER "3.19.0")
add_library(rublon-ssh_ide INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/authentication_step_interface.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/configuration.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/init.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_factory.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/OTP.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/SMS.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
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/sign.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/span.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/utils.hpp
)
endif()
target_include_directories(rublon-ssh
INTERFACE

View File

@ -9,10 +9,10 @@ template < typename Impl >
class AuthenticationStep {
public:
template < typename Handler_t >
auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) {
auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) const {
// log step
log(Info, "Starting %s step", static_cast< Impl * >(this)->name);
return static_cast< Impl * >(this)->handle(coreHandler);
log(Info, "Starting %s step", static_cast< const Impl * >(this)->name);
return static_cast< const Impl * >(this)->handle(coreHandler);
}
};

View File

@ -42,22 +42,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
HttpHandler http{};
public:
void debugLog(const char *, const char *) {}
CoreHandler(const rublon::Configuration & config) : secretKey{config.parameters.secretKey}, url{config.parameters.apiServer} {}
tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const {
rublon::log(LogLevel::Info, "Sending request to %s", path.data());
log(Debug, "CoreHandler::Request prepare");
std::byte _buffer[16 * 1024];
std::pmr::monotonic_buffer_resource mr{_buffer, sizeof(_buffer)};
rublon::RapidJSONPMRAlloc alloc{&mr};
rublon::StringBuffer jsonStr{&alloc};
rublon::Writer writer{jsonStr, &alloc};
body.Accept(writer);
Request request;
request.headers["Content-Type"] = "application/json";
request.headers["Accept"] = "application/json";
@ -65,7 +62,6 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
request.body = jsonStr.GetString();
signRequest(mr, request);
std::pmr::string uri{url + path.data(), &mr};
auto response = http.request(uri, request);

View File

@ -21,7 +21,7 @@ template < typename Impl >
class CoreHandlerInterface {
public:
tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const {
rublon::log(LogLevel::Debug, "%s", __PRETTY_FUNCTION__);
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request");
return static_cast< const Impl * >(this)->request(path, body);
}
};

View File

@ -46,6 +46,7 @@ class CURL {
/// TODO this can be done on stack using pmr
auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all);
std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) {
log(Debug, "header: %s: %s", header.first.c_str(), header.second.c_str());
curl_headers.reset(curl_slist_append(curl_headers.release(), (header.first + ": " + header.second).c_str()));
});
@ -59,15 +60,15 @@ class CURL {
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data);
rublon::log(LogLevel::Info, "Request send uri:{%s} body:{%s}", uri.data(), request.body.c_str());
log(Info, "Request send uri:%s body:\n%s\n", uri.data(), request.body.c_str());
auto res = curl_easy_perform(curl.get());
if(res != CURLE_OK) {
log(LogLevel::Error, "No response from Rublon server err:{%s}", curl_easy_strerror(res));
log(Error, "No response from Rublon server err:{%s}", curl_easy_strerror(res));
return std::nullopt;
}
log(LogLevel::Debug, "Response data :{{ %s }}", response_data.c_str());
log(Debug, "Response:\n%s\n", response_data.c_str());
long size;
curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size);

View File

@ -9,51 +9,55 @@
#include <rublon/method/method_factory.hpp>
namespace rublon{
class Verify{};
}
namespace rublon {
template < template < typename > class MethodFactory_t = MethodFactory, typename PamInfo_t = LinuxPam >
class Init : public AuthenticationStep< Init< MethodFactory_t, PamInfo_t > > {
const char * apiPath = "/api/transaction/init";
const std::string & _systemToken;
protected:
PamInfo_t _pamInfo;
MethodFactory_t< PamInfo_t > _methodFactory;
public:
const char * name = "Initialization";
Init(pam_handle_t * pamHandler, const rublon::Configuration & config)
: _systemToken{config.parameters.systemToken}, _pamInfo{pamHandler}, _methodFactory{_pamInfo} {}
/// TODO add core handler interface
: _systemToken{config.parameters.systemToken}, _pamInfo{pamHandler}, _methodFactory{_pamInfo, config.parameters.systemToken} {}
/// TODO add core handler interface
template < typename Hander_t >
tl::expected< Method, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
tl::expected< Method<PamInfo_t>, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
char _buffer[1024];
std::pmr::monotonic_buffer_resource mr{_buffer, 1024};
RapidJSONPMRAlloc alloc{&mr};
Document body{rapidjson::kObjectType, &alloc};
body.AddMember("systemToken", Value{_systemToken.c_str(), alloc}, alloc);
body.AddMember("username", Value{_pamInfo.username().get(), alloc}, alloc);
body.AddMember("userEmail", "bwi@rublon.com", alloc); /// TODO proper username
Value params{rapidjson::kObjectType};
params.AddMember("userIP", Value{_pamInfo.ip().get(), alloc}, alloc);
params.AddMember("appVer", "v.1.6", alloc); /// TODO add version to cmake
params.AddMember("os", "Ubuntu 23.04", alloc); /// TODO add version to cmake
body.AddMember("params", std::move(params), alloc);
auto httpResponse = coreHandler.request(apiPath, body);
if(httpResponse.has_value()) {
auto coreResponse = coreHandler.request(apiPath, body);
if(coreResponse.has_value()) {
log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__);
const auto & rublonResponse = httpResponse.value()["result"];
std::string tid = rublonResponse["tid"].GetString();
const auto & rublonResponse = coreResponse.value()["result"];
std::string tid = rublonResponse["tid"].GetString();
return _methodFactory.create(rublonResponse["methods"].GetArray(), std::move(tid));
} else {
// mostly connectio errors
switch(httpResponse.error().errorClass) {
switch(coreResponse.error().errorClass) {
case CoreHandlerError::ErrorClass::BadSigature:
log(LogLevel::Error, "ErrorClass::BadSigature");
return tl::unexpected{PamAction::decline};
@ -68,8 +72,8 @@ class Init : public AuthenticationStep< Init< MethodFactory_t, PamInfo_t > > {
return tl::unexpected{PamAction::decline};
}
}
return tl::unexpected{PamAction::decline};
}
};
}
} // namespace rublon

View File

@ -3,12 +3,71 @@
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
namespace rublon {
class Confirmation {};
}; // namespace rublon
namespace rublon::method {
class OTP : public rublon::AuthenticationStep< OTP > {
template < typename PamInfo_t = LinuxPam >
class OTP : public rublon::AuthenticationStep< OTP< PamInfo_t > > {
public:
const char * name = "Select 'One Time Password' method";
const char * uri = "/api/transaction/confirmCode";
const char * name = "One Time Password";
std::string _systemToken;
std::string _tid;
const PamInfo_t & _pamInfo;
OTP(std::string systemToken, std::string tid, const PamInfo_t & pam) : _systemToken{systemToken}, _tid{tid}, _pamInfo{pam} {}
template < typename Hander_t >
tl::expected< Confirmation, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
log(Debug, "OTP fired");
const auto passcode =
_pamInfo.scan([](const char * userInput) { return std::string{userInput}; }, "Mobile TOTP from Rublon Authenticator:");
char _buffer[1024];
std::pmr::monotonic_buffer_resource mr{_buffer, 1024};
RapidJSONPMRAlloc alloc{&mr};
Document body{rapidjson::kObjectType, &alloc};
body.AddMember("systemToken", Value{_systemToken.c_str(), alloc}, alloc);
body.AddMember("tid", Value{_tid.c_str(), alloc}, alloc);
body.AddMember("vericode", Value{passcode.value().c_str(), alloc}, alloc); /// TODO proper username
auto coreResponse = coreHandler.request(uri, body);
if(coreResponse.has_value()) {
log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__);
const auto & rublonResponse = coreResponse.value()["result"];
std::string tid = rublonResponse["tid"].GetString();
return tl::unexpected{PamAction::accept};
} else {
// mostly connectio errors
switch(coreResponse.error().errorClass) {
case CoreHandlerError::ErrorClass::BadSigature:
log(LogLevel::Error, "ErrorClass::BadSigature");
return tl::unexpected{PamAction::decline};
case CoreHandlerError::ErrorClass::CoreException: /// TODO exception handling
log(LogLevel::Error, "ErrorClass::CoreException");
return tl::unexpected{PamAction::decline}; /// TODO accept?
case CoreHandlerError::ErrorClass::ConnectionError:
log(LogLevel::Error, "ErrorClass::ConnectionError");
return tl::unexpected{PamAction::decline}; /// TODO decline?
case CoreHandlerError::ErrorClass::BrokenData:
log(LogLevel::Error, "ErrorClass::BrokenData");
return tl::unexpected{PamAction::decline};
}
}
return tl::unexpected{PamAction::accept};
}
};
} // namespace rublon::method

View File

@ -1,5 +1,8 @@
#pragma once
namespace rublon::method {
class SMS {};
} // namespace rublon
class SMS {
public:
const char * name = "SMS";
};
} // namespace rublon::method

View File

@ -12,34 +12,83 @@
#include <rublon/method/SMS.hpp>
namespace rublon {
template < typename Pam_t = LinuxPam >
class Method {
std::string tid;
std::string _tid;
std::string _systemToken;
public:
Method() {}
template < typename Method_t >
Method(Method_t && metho, std::string systemToken, std::string tid) : _impl{metho}, _systemToken{systemToken}, _tid{tid}{}
template < typename Handler_t >
tl::expected< int, PamAction > fire(const CoreHandlerInterface< Handler_t > & coreHandler) {
return std::visit([&](const auto & method) { return method.fire(coreHandler); }, _impl);
tl::expected< Confirmation, PamAction > fire(const CoreHandlerInterface< Handler_t > & coreHandler) const {
log(Debug, "method select");
char _buffer[1024];
std::pmr::monotonic_buffer_resource mr{_buffer, 1024};
RapidJSONPMRAlloc alloc{&mr};
Document body{rapidjson::kObjectType, &alloc};
body.AddMember("systemToken", Value{_systemToken.c_str(), alloc}, alloc);
body.AddMember("tid", Value{_tid.c_str(), alloc}, alloc);
body.AddMember("method", Value{"totp", alloc}, alloc);
body.AddMember("GDPRAccepted", Value{"true", alloc}, alloc);
body.AddMember("tosAccepted", Value{"true", alloc}, alloc);
auto coreResponse = coreHandler.request("/api/transaction/methodSSH", body);
if(coreResponse.has_value()) {
log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__);
const auto & rublonResponse = coreResponse.value()["result"];
std::string tid = rublonResponse["tid"].GetString();
return tl::unexpected{PamAction::accept};
} else {
// mostly connectio errors
switch(coreResponse.error().errorClass) {
case CoreHandlerError::ErrorClass::BadSigature:
log(LogLevel::Error, "ErrorClass::BadSigature");
return tl::unexpected{PamAction::decline};
case CoreHandlerError::ErrorClass::CoreException: /// TODO exception handling
log(LogLevel::Error, "ErrorClass::CoreException");
return tl::unexpected{PamAction::decline}; /// TODO accept?
case CoreHandlerError::ErrorClass::ConnectionError:
log(LogLevel::Error, "ErrorClass::ConnectionError");
return tl::unexpected{PamAction::decline}; /// TODO decline?
case CoreHandlerError::ErrorClass::BrokenData:
log(LogLevel::Error, "ErrorClass::BrokenData");
return tl::unexpected{PamAction::decline};
}
}
return std::visit(
[&](const auto & method) {
rublon::log(LogLevel::Info, "Using '%s' method", method.name);
return method.fire(coreHandler);
},
_impl);
}
private:
std::variant< method::OTP, method::SMS > _impl;
std::variant< method::OTP<Pam_t> > _impl;
};
template < typename Pam_t = LinuxPam >
class MethodFactory {
std::string systemToken;
const Pam_t & pam;
public:
MethodFactory(const Pam_t & pam) : pam{pam} {}
MethodFactory(const Pam_t & pam, std::string systemToken) : pam{pam}, systemToken{systemToken} {}
template < typename Array_t >
tl::expected< Method, PamAction > create(const Array_t & methods, const std::string & tid) const {
tl::expected< Method<Pam_t>, PamAction > create(const Array_t & methods, const std::string & tid) const {
std::pmr::map< int, std::pmr::string > methods_id;
pam.print("%s", "");
int i;
int i{};
for(const auto & method : methods) {
rublon::log(LogLevel::Debug, "method %s found at pos %d", method.GetString(), i);
if(method == "email") {
@ -74,7 +123,11 @@ class MethodFactory {
pam.print(
"you selected: %s", methods_id.count(methodid.value_or(0)) ? methods_id.at(methodid.value_or(0)).c_str() : "unknown option");
if(methods_id.at(methodid.value_or(0)) == "totp"){
return Method{method::OTP<Pam_t>{systemToken,std::move(tid), pam}, systemToken, tid};
}
return tl::unexpected{PamAction::accept};
}
};

View File

@ -27,8 +27,6 @@ class Confirm : public AuthenticationStep< Confirm > {
Confirm(const Configuration & /*config*/) {}
};
class VerifySSH : public AuthenticationStep< VerifySSH > {
public:
VerifySSH(const Configuration & /*config*/) {}

View File

@ -16,6 +16,7 @@
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <syslog.h>
#include <unistd.h>
namespace rublon {
enum LogLevel { Debug, Info, Warning, Error };
@ -40,19 +41,20 @@ namespace details{
if(fp) {
fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[(int)level], line);
sync();
}
}
}
inline void log(LogLevel level, const char * line) noexcept {
if(level > g_level)
if(level < g_level)
return;
details::doLog(level, line);
}
template < typename... Ti >
void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept {
if(level > g_level)
if(level < g_level)
return;
std::array< char, 1000 > line;
@ -98,7 +100,7 @@ namespace details {
std::transform(value.cbegin(), value.cend(), buf, asciitolower);
return strcmp(buf, "true") == 0;
};
}
static inline std::string_view ltrim(std::string_view s) {
while(s.length() && std::isspace(*s.begin()))

View File

@ -12,14 +12,15 @@ target_compile_options(rublon-ssh-pam
-fdata-sections
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-flto
)
target_link_options(rublon-ssh-pam
PUBLIC
-fpic
-s
# -static-libstdc++
-Wl,--gc-sections
-flto
)
target_link_libraries(rublon-ssh-pam

View File

@ -11,7 +11,7 @@
#include <rublon/init.hpp>
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_PUBLIC __attribute__((visibility("default")))
using namespace std;
@ -20,32 +20,36 @@ namespace {
template < typename T >
using Expected = tl::expected< T, rublon::PamAction >;
Expected< rublon::Method > chooseMethod() {}
Expected< rublon::Confirm > confirm() {}
} // namespace
DLL_PUBLIC int pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char ** argv) {
DLL_PUBLIC int pam_sm_setcred([[maybe_unused]] pam_handle_t * pamh,
[[maybe_unused]] int flags,
[[maybe_unused]] int argc,
[[maybe_unused]] const char ** argv) {
return PAM_SUCCESS;
}
DLL_PUBLIC int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char ** argv) {
DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh,
[[maybe_unused]] int flags,
[[maybe_unused]] int argc,
[[maybe_unused]] const char ** argv) {
return PAM_SUCCESS;
}
DLL_PUBLIC int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) {
DLL_PUBLIC int
pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
rublon::log(rublon::LogLevel::Debug, "pam start");
auto rublonConfig = rublon::ConfigurationFactory{}.systemConfig();
rublon::CoreHandler CH{rublonConfig.value()};
rublon::log(rublon::LogLevel::Debug, "Init");
auto method = rublon::Init{pamh, rublonConfig.value()}.fire(CH);
rublon::log(rublon::LogLevel::Debug, "Method");
method.value().fire(CH);
auto action = rublon::Init{pamh, rublonConfig.value()}
.fire(CH) //
.and_then([](const auto & method) -> Expected< int > {
return tl::unexpected{rublon::PamAction::accept};
});
// .and_then([](const auto & value) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; }) //
// .and_then([](const auto & conf) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; });
// .and_then([](const auto & value) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; }) //
// .and_then([](const auto & conf) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; });
return PAM_SUCCESS;
}

View File

@ -64,9 +64,10 @@ class PamInfoMock {
template < typename Pam >
class MethodFactoryMock {
public:
using MethodFactoryCreate_t = tl::expected< Method, PamAction >;
MethodFactoryMock(const Pam &) {}
using MethodFactoryCreate_t = tl::expected< Method<Pam>, PamAction >;
template<typename ... Args>
MethodFactoryMock(Args && ... ) {}
MOCK_METHOD(MethodFactoryCreate_t, create_mocked, (std::vector< std::string > methods, std::string tid), (const));