This commit is contained in:
Bartosz Wieczorek 2023-08-22 13:34:40 +02:00
parent fa748d0a2c
commit 700845e17a
19 changed files with 217 additions and 167 deletions

View File

@ -15,9 +15,10 @@ if(${CMAKE_VERSION} VERSION_GREATER "3.19.0")
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.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/method_select.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/method/passcode_based_auth.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,7 +39,7 @@ class AuthenticationStep {
}
template < typename HandlerReturn_t >
Error coreErrorHandler(const HandlerReturn_t & coreResponse) const {
Error coreErrorHandler(const HandlerReturn_t & /*coreResponse*/) const {
// switch(coreResponse.error().errorClass) {
// case CoreHandlerError::ErrorClass::BadSigature:
// log(LogLevel::Error, "ErrorClass::BadSigature");

View File

@ -40,7 +40,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
HttpHandler http{};
public:
CoreHandler(const rublon::Configuration & config)
CoreHandler(const Configuration & config)
: secretKey{config.parameters.secretKey}, url{config.parameters.apiServer}, http{[]() { return Response{}; }} {}
tl::expected< Response, Error > validateSignature(const Response & response) const {
@ -52,8 +52,8 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
}
tl::expected< Document, Error > validateResponse(const Response & response) const {
rublon::RapidJSONPMRStackAlloc< 4 * 1024 > alloc{};
rublon::Document resp{&alloc};
RapidJSONPMRStackAlloc< 4 * 1024 > alloc{};
Document resp{&alloc};
resp.Parse(response.body.c_str());
if(resp.HasParseError() or not resp.HasMember("result")) {
@ -70,19 +70,16 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
return resp;
}
Request generateRequest(std::string_view path, const rublon::Document & body){
tl::expected< Document, Error > request(std::string_view path, const Document & body) const {
const auto validateSignature = [this](const auto & arg) { return this->validateSignature(arg); };
const auto validateResponse = [this](const auto & arg) { return this->validateResponse(arg); };
}
tl::expected< Document, Error > request(std::string_view path, const rublon::Document & body) const {
auto bind_this = [this](auto memfn) { return [&](const auto & arg) { return (*this.*memfn)(arg); }; };
rublon::RapidJSONPMRStackAlloc< 4 * 1024 > alloc{};
rublon::StringBuffer jsonStr{&alloc};
rublon::Writer writer{jsonStr, &alloc};
RapidJSONPMRStackAlloc< 1 * 1024 > alloc{};
StringBuffer jsonStr{&alloc};
Writer writer{jsonStr, &alloc};
body.Accept(writer);
std::byte _buffer[8 * 1024];
std::byte _buffer[2 * 1024];
std::pmr::monotonic_buffer_resource mr{_buffer, sizeof(_buffer)};
Request request{mr};
@ -95,8 +92,8 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
std::pmr::string uri{url + path.data(), &mr};
return http.request(uri, request)
.and_then(bind_this(&CoreHandler::validateSignature))
.and_then(bind_this(&CoreHandler::validateResponse))
.and_then(validateSignature)
.and_then(validateResponse)
.or_else([](const Error & e) -> tl::expected< Document, Error > { return tl::unexpected{e}; });
}
};

View File

@ -22,10 +22,8 @@ namespace rublon {
namespace {
size_t WriteMemoryCallback(void * contents, size_t size, size_t nmemb, void * userp) {
log(LogLevel::Debug, "got %d/%d content : %s", size, nmemb, ( const char * ) contents);
size_t realsize = size * nmemb;
reinterpret_cast< std::string * >(userp)->append(static_cast< const char * >(contents), realsize);
const size_t realsize = size * nmemb;
reinterpret_cast< std::pmr::string * >(userp)->append(static_cast< const char * >(contents), realsize);
return realsize;
}
} // namespace
@ -55,7 +53,7 @@ class CURL {
std::array< char, 16 * 1024 > buffer = {};
std::pmr::monotonic_buffer_resource mr{buffer.data(), buffer.size()};
std::pmr::string response_data;
std::pmr::string response_data{&mr};
response_data.reserve(15000);
/// TODO this can be done on stack using pmr
@ -81,13 +79,13 @@ class CURL {
log(LogLevel::Error, "%s No response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res));
return tl::unexpected{Error{SocketError{SocketError::Timeout}}};
}
log(LogLevel::Debug, "Response:\n%s\n", response_data.c_str());
Response response;
long size;
curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size);
/// TODO ogarnąć alokację pamięci
response.headers = details::headers({response_data.data(), static_cast< std::size_t >(size)});
response.body = response_data.substr(size);

View File

@ -16,7 +16,6 @@ class SocketError {
ErrorClass errorClass;
std::string reson;
std::error_code d;
// error_category interface
public:
@ -24,7 +23,7 @@ class SocketError {
return "SockerError";
}
std::string message(int) const {
return reson;
return "";
}
};
@ -41,16 +40,33 @@ class CoreHandlerError {
return "CoreHandlerError";
}
std::string message(int) const {
return reson;
return "";
}
};
class MethodError {
public:
enum ErrorClass { BadMethod };
MethodError(ErrorClass e) : errorClass{e} {}
MethodError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {}
ErrorClass errorClass;
std::string reson;
const char * name() const noexcept {
return "MethodError";
}
std::string message(int) const {
return "";
}
};
class Critical {};
class Error {
std::variant< std::monostate, CoreHandlerError, SocketError, Critical > _error;
std::variant< std::monostate, CoreHandlerError, SocketError, Critical, MethodError > _error;
enum class Category { None, CoreHandlerError, SockerError, Criticat };
enum class Category { None, CoreHandlerError, SockerError, Criticat, MethodError };
public:
Error() = default;
@ -58,6 +74,7 @@ class Error {
Error(CoreHandlerError error) : _error{error} {}
Error(SocketError error) : _error{error} {}
Error(Critical error) : _error{error} {}
Error(MethodError error) : _error{error} {}
Error(const Error &) = default;
Error(Error &&) = default;
@ -73,7 +90,7 @@ class Error {
return _error.index() == 2;
}
Category category()const noexcept{
Category category() const noexcept {
return static_cast< Category >(_error.index());
}
};

View File

@ -6,7 +6,7 @@
#include <rublon/authentication_step_interface.hpp>
#include <rublon/configuration.hpp>
#include <rublon/method/method_factory.hpp>
#include <rublon/method/method_select.hpp>
namespace rublon {
class Verify {};

View File

@ -6,40 +6,14 @@
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
#include <rublon/method/passcode_based_auth.hpp>
namespace rublon::method {
class OTP : public AuthenticationStep< OTP > {
using base_t = AuthenticationStep< OTP >;
const char * uri = "/api/transaction/confirmCode";
class OTP:public PasscodeBasedAuth{
public:
const char * name = "TOTP";
OTP(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Mobile TOTP from Rublon Authenticator:") {}
OTP(std::string systemToken, std::string tid) : base_t(std::move(systemToken), std::move(tid)) {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
RapidJSONPMRStackAlloc< 1024 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
this->addSystemToken(body, alloc);
this->addTid(body, alloc);
const auto passcode =
pam.scan([](const char * userInput) { return std::string{userInput}; }, "Mobile TOTP from Rublon Authenticator:");
body.AddMember("vericode", Value{passcode.value().c_str(), alloc}, alloc); /// TODO proper username
auto coreResponse = coreHandler.request(uri, body);
if(coreResponse.has_value()) {
// const auto & rublonResponse = coreResponse.value()["result"];
// {"status":"OK","result":true}
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
} else {
return tl::unexpected{this->coreErrorHandler(coreResponse)};
}
}
};
} // namespace rublon::method

View File

@ -6,39 +6,14 @@
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
#include <rublon/method/passcode_based_auth.hpp>
namespace rublon::method {
class SMS : public AuthenticationStep< SMS > {
using base_t = AuthenticationStep< SMS >;
const char * uri = "/api/transaction/confirmCode";
class SMS:public PasscodeBasedAuth{
public:
const char * name = "SMS";
SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "SMS passcode:") {}
SMS(std::string systemToken, std::string tid) : base_t(std::move(systemToken), std::move(tid)) {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
RapidJSONPMRStackAlloc< 1024 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
this->addSystemToken(body, alloc);
this->addTid(body, alloc);
const auto passcode = pam.scan([](const char * userInput) { return std::string{userInput}; }, "SMS passcode:");
body.AddMember("vericode", Value{passcode.value().c_str(), alloc}, alloc);
auto coreResponse = coreHandler.request(uri, body);
if(coreResponse.has_value()) {
// const auto & rublonResponse = coreResponse.value()["result"];
// {"status":"OK","result":true}
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
} else {
return tl::unexpected{this->coreErrorHandler(coreResponse)};
}
}
};
} // namespace rublon::method

View File

@ -11,18 +11,40 @@
#include <rublon/method/OTP.hpp>
#include <rublon/method/SMS.hpp>
template < class F >
struct return_type;
template < class R, class... A >
struct return_type< R (*)(A...) > {
typedef R type;
};
template < typename T >
using return_type_t = typename return_type< T >::type;
namespace std {
inline ::rublon::Value::ConstValueIterator begin(const rublon::Value & __ils) noexcept {
return __ils.Begin();
}
inline ::rublon::Value::ConstValueIterator end(const rublon::Value & __ils) noexcept {
return __ils.End();
}
[[nodiscard]] inline std::size_t size(const rublon::Value & __cont) {
return __cont.Size();
}
} // namespace std
namespace rublon {
class Method : public AuthenticationStep< Method > {
using base_t = AuthenticationStep< Method >;
class MethodProxy {
public:
template < typename Method_t >
Method(Method_t method, std::string systemToken, std::string tid) : base_t(std::move(systemToken), std::move(tid)), _impl{method} {}
MethodProxy(Method_t method) : _impl{std::move(method)} {}
template < typename Handler_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > fire(const CoreHandlerInterface< Handler_t > & coreHandler,
const PamInfo_t & pam) const {
tl::expected< AuthenticationStatus, Error > fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const {
return std::visit(
[&](const auto & method) {
rublon::log(LogLevel::Info, "Using '%s' method", method.name);
@ -41,6 +63,20 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
const char * uri = "/api/transaction/methodSSH";
std::string _method;
tl::expected< MethodProxy, Error > doCreateMethod(const Document & coreResponse) const {
const auto & rublonResponse = coreResponse["result"];
std::string tid = rublonResponse["tid"].GetString();
if(_method == "totp") {
return MethodProxy{method::OTP{this->_systemToken, std::move(tid)}};
} else if(_method == "sms") {
return MethodProxy{method::SMS{this->_systemToken, std::move(tid)}};
}
else
return tl::unexpected{MethodError{MethodError::BadMethod}};
}
public:
const char * name = "Confirm Method";
@ -48,7 +84,9 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
: base_t(std::move(systemToken), std::move(tid)), _method{method} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< Method, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const {
auto createMethod = [&](const auto & coreResponse) { return doCreateMethod(coreResponse); };
RapidJSONPMRStackAlloc< 1024 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
@ -59,41 +97,27 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
body.AddMember("GDPRAccepted", "true", alloc);
body.AddMember("tosAccepted", "true", alloc);
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();
// if(_method == "totp") {
// return Method{method::OTP{this->_systemToken, this->_tid}, this->_systemToken, this->_tid};
// } else if(_method == "sms") {
// return Method{method::SMS{this->_systemToken, this->_tid}, this->_systemToken, this->_tid};
// }
return Method{method::SMS{this->_systemToken, this->_tid}, this->_systemToken, this->_tid};
} else {
// return tl::unexpected{this->coreErrorHandler(coreResponse)};
return tl::unexpected{Critical{}};
}
// return tl::unexpected{PamAction::accept};
return tl::unexpected{Critical{}};
return coreHandler
.request(uri, body) //
.and_then(createMethod);
}
};
class MethodSelect {
std::string systemToken;
std::string _systemToken;
std::string _tid;
std::vector< std::string > _methods;
public:
template < typename Array_t >
MethodSelect(std::string systemToken, std::string tid, const Array_t & methods) : systemToken{systemToken}, _tid{std::string{tid}} {
for(auto it = methods.Begin(); it < methods.End(); it++) {
_methods.emplace_back(it->GetString());
}
MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser)
: _systemToken{std::move(systemToken)}, _tid{std::move(tid)} {
_methods.reserve(std::size(methodsAvailableForUser));
std::transform(
std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) {
return method.GetString();
});
}
template < typename Pam_t >
@ -136,11 +160,12 @@ class MethodSelect {
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 PostMethod{systemToken, _tid, methods_id.at(methodid.value_or(0))};
/// TODO check if valid method
if(auto it = methods_id.find(methodid.value()); it != methods_id.end()) {
return PostMethod{_systemToken, _tid, it->second};
}
// return tl::unexpected{PamAction::accept};
return tl::unexpected{Critical{}};
}
};

View File

@ -0,0 +1,50 @@
#pragma once
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
namespace rublon::method {
class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
using base_t = AuthenticationStep< PasscodeBasedAuth >;
const char * uri = "/api/transaction/confirmCode";
const char * userMessage{nullptr};
template < typename PamInfo_t = LinuxPam >
std::string readPasscode(const PamInfo_t & pam) const {
/// TODO handle bad user input / wrong code etc
return pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage).value();
}
public:
const char * name;
PasscodeBasedAuth(std::string systemToken, std::string tid, const char * name, const char * userMessage)
: base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, name{name} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
RapidJSONPMRStackAlloc< 1024 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
this->addSystemToken(body, alloc);
this->addTid(body, alloc);
body.AddMember("vericode", Value{readPasscode(pam).c_str(), alloc}, alloc); /// TODO proper username
auto coreResponse = coreHandler.request(uri, body);
if(coreResponse.has_value()) {
// {"status":"OK","result":{"error":"Hmm, that's not the right code. Try again."}}
// const auto & rublonResponse = coreResponse.value()["result"];
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
} else {
return tl::unexpected{this->coreErrorHandler(coreResponse)};
}
}
};
} // namespace rublon::method

View File

@ -47,7 +47,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); };
auto verifi = [&](const Method & method) { return method.fire(CH, pam); };
auto verifi = [&](const MethodProxy & method) { return method.fire(CH, pam); };
auto authStatus = Init{rublonConfig.value()}
.fire(CH, pam) //

View File

@ -23,7 +23,8 @@ add_executable(rublon-tests
./authentication_step_common_tests.cpp
./core_handler_tests.cpp
./init_test.cpp
./method_factory_test.cpp
./method_select_tests.cpp
./passcode_auth_tests.cpp
./rublon_tests.cpp
./sign_tests.cpp
./utils_tests.cpp

View File

@ -1,33 +1,3 @@
#include <gtest/gtest.h>
#include <rublon/authentication_step_interface.hpp>
#include <tl/expected.hpp>
class err {};
class err2 {};
struct expect {
int s;
};
struct moreexpected {
int a;
};
tl::expected< expect, err > foo(int a) {
if(a > 10) {
return tl::unexpected{err{}};
}
return expect{4 * a};
}
TEST(expected, tests) {
auto value = foo(11)
.and_then([](const expect & expect) { return tl::expected< moreexpected, err >{moreexpected{expect.s * 2}}; })
.or_else([](const err & error) { return tl::expected< moreexpected, err >{moreexpected{5}}; })
.and_then([](const moreexpected &expected){return tl::expected<int, err>{6*expected.a};} )
;
EXPECT_EQ(*value, 5 * 6);
}

View File

@ -0,0 +1,7 @@
#include <rublon/core_handler_interface.hpp>
namespace mocks {
class CoreHandlerMock : public rublon::CoreHandlerInterface< CoreHandlerMock > {};
} // namespace mocks

View File

@ -18,7 +18,7 @@ constexpr T forward_or_transform(T t) {
}
template < class T, class = is_string< T > >
constexpr const char * forward_or_transform(T t) {
constexpr const char * forward_or_transform(const T &t) {
return t.c_str();
}
@ -63,13 +63,13 @@ class CoreResponseGenerator {
return _buf.data();
}
CoreResponseGenerator & methods(std::initializer_list< std::string > newMethods) {
CoreResponseGenerator & withMethods(std::initializer_list< std::string > newMethods) {
_methods.clear();
std::copy(newMethods.begin(), newMethods.end(), std::inserter(_methods, _methods.begin()));
return *this;
}
CoreResponseGenerator & tid(std::string tid) {
CoreResponseGenerator & withTid(std::string tid) {
_tid = tid;
return *this;
}

View File

@ -29,12 +29,12 @@ class CoreHandlerMock : public CoreHandlerInterface< CoreHandlerMock > {
}
CoreHandlerMock & methods(std::initializer_list< std::string > methods) {
gen.methods(methods);
gen.withMethods(methods);
return *this;
}
CoreHandlerMock & tid(std::string tid) {
gen.tid(tid);
gen.withTid(tid);
return *this;
}
@ -62,7 +62,7 @@ class PamInfoMock {
class MethodFactoryMock {
public:
using MethodFactoryCreate_t = tl::expected< Method, Error >;
using MethodFactoryCreate_t = tl::expected< MethodProxy, Error >;
template < typename... Args >
MethodFactoryMock(Args &&...) {}

View File

@ -0,0 +1,12 @@
#include <gmock/gmock.h>
#include <rublon/method/method_select.hpp>
using namespace rublon;
using namespace testing;
class MethodSelectTests : public Test {
public:
PostMethod sut;
};

View File

@ -0,0 +1,23 @@
#include <gmock/gmock.h>
#include <rublon/method/passcode_based_auth.hpp>
#include "core_handler_mock.hpp"
using namespace testing;
using namespace rublon;
class PasscodeBasedAuthTest : public Test {
public:
PasscodeBasedAuthTest() : sut{systemToken, tid, name, userMessage} {}
std::string systemToken, tid;
const char * name = "Test";
const char * userMessage = "message";
method::PasscodeBasedAuth sut;
// co
};
TEST_F(PasscodeBasedAuthTest, wrongPasscodeShouldFail){
// sut.handle()
}