Bwi/bugfix (#7)

* generate user enrolement message
* Fix bugs found during testing
This commit is contained in:
rublon-bwi 2024-01-25 16:30:12 +01:00 committed by GitHub
parent 7715b6fb45
commit c3127e8b58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 510 additions and 399 deletions

View File

@ -30,6 +30,10 @@ install(
share/rublon
COMPONENT
PAM
PERMISSIONS
OWNER_READ
OWNER_WRITE
GROUP_READ
)
if (${ENABLE_TESTS})

View File

@ -1,15 +1,10 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <map>
#include <memory_resource>
#include <optional>
#include <sstream>
#include <string>
#include "utils.hpp"
@ -26,17 +21,15 @@ class ConfigurationError {
class Configuration {
public:
struct Parameters {
std::string systemToken;
std::string secretKey;
std::string apiServer;
int prompt;
bool enablePasswdEmail;
bool logging;
bool autopushPrompt;
bool bypass;
bool offlineBypas;
} parameters;
std::array< char, 33 > systemToken{};
std::array< char, 33 > secretKey{};
std::array< char, 300 > apiServer{};
int prompt{};
bool enablePasswdEmail{};
bool logging{};
bool autopushPrompt{};
bool bypass{};
bool offlineBypas{};
};
namespace {
@ -45,11 +38,24 @@ namespace {
template < typename T >
tl::expected< T, ConfigurationError > to(std::string_view);
template <>
auto to(std::string_view arg) -> tl::expected< std::string, ConfigurationError > {
return std::string{arg.data(), arg.size()};
auto to(std::string_view arg) -> tl::expected< std::array<char, 33>, ConfigurationError > {
assert(arg.size()<=(33-1));
std::array<char, 33> value{};
std::memcpy(value.data(), arg.data(), arg.size());
return value;
}
template <>
auto to(std::string_view arg) -> tl::expected< std::array<char, 300>, ConfigurationError > {
assert(arg.size()<=(300-1));
std::array<char, 300> value{};
std::memcpy(value.data(), arg.data(), arg.size());
return value;
}
template <>
auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > {
return conv::to_bool(arg);
@ -67,15 +73,15 @@ struct Entry {
using pType = decltype(member_ptr_t(member));
return [](const Entry * _this,
Configuration::Parameters * params,
Configuration * configuration,
std::string_view userInput) -> tl::expected< Source, ConfigurationError > {
const auto setDefaultValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
params->*member = value;
configuration->*member = value;
return Source::DefaultValue;
};
const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > {
params->*member = value;
configuration->*member = value;
return Source::UserInput;
};
@ -97,10 +103,9 @@ struct Entry {
const char * name;
const char * defaultValue;
tl::expected< Source, ConfigurationError > (
*_read)(const Entry * _this, Configuration::Parameters * params, std::string_view userInput);
tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration * configuration, std::string_view userInput);
bool read(Configuration::Parameters * params, std::optional< std::string_view > userInput) const {
bool read(Configuration * configuration, 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,
@ -118,7 +123,7 @@ struct Entry {
return tl::unexpected{error};
};
return _read(this, params, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value();
return _read(this, configuration, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value();
}
};
@ -127,16 +132,15 @@ constexpr auto make_entry(const char * name, const char * defaultValue) {
return Entry{name, defaultValue, Entry::make_read_function< member >()};
}
using P = Configuration::Parameters;
constexpr static inline std::array< Entry, 8 > configurationVariables = { //
make_entry< &P::logging >("logging", "true"),
make_entry< &P::systemToken >("systemToken", nullptr),
make_entry< &P::secretKey >("secretKey", nullptr),
make_entry< &P::apiServer >("rublonApiServer", nullptr),
make_entry< &P::prompt >("prompt", "1"),
make_entry< &P::enablePasswdEmail >("enablePasswdEmail", "true"),
make_entry< &P::autopushPrompt >("autopushPrompt", "false"),
make_entry< &P::offlineBypas >("failMode", "bypas")};
make_entry< &Configuration::logging >("logging", "true"),
make_entry< &Configuration::systemToken >("systemToken", nullptr),
make_entry< &Configuration::secretKey >("secretKey", nullptr),
make_entry< &Configuration::apiServer >("rublonApiServer", nullptr),
make_entry< &Configuration::prompt >("prompt", "1"),
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
make_entry< &Configuration::offlineBypas >("failMode", "bypas")};
class ConfigurationFactory {
public:
@ -144,8 +148,7 @@ class ConfigurationFactory {
std::optional< Configuration > systemConfig() {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
using Params = Configuration::Parameters;
Params configValues;
Configuration configuration{};
std::ifstream file(std::filesystem::path{"/etc/rublon.config"});
if(not file.good())
@ -177,11 +180,11 @@ class ConfigurationFactory {
}
for(const auto & entry : configurationVariables) {
if(not entry.read(&configValues, readParameterByName(entry.name)))
if(not entry.read(&configuration, readParameterByName(entry.name)))
return std::nullopt;
}
return Configuration{std::move(configValues)};
return configuration;
}
};
} // namespace rublon

View File

@ -3,15 +3,14 @@
#include <memory_resource>
#include <string>
#include "configuration.hpp"
#include "curl.hpp"
#include "json.hpp"
#include "sign.hpp"
#include <rublon/configuration.hpp>
#include <rublon/core_handler_interface.hpp>
#include <rublon/curl.hpp>
#include <rublon/json.hpp>
#include <rublon/sign.hpp>
#include <tl/expected.hpp>
#include <rublon/core_handler_interface.hpp>
namespace rublon {
template < typename HttpHandler = CURL >
@ -32,13 +31,29 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
return signatureMatch;
}
bool containsException(const Document & coreResponse) const {
// if(resp.HasMember("status") and resp["status"].IsObject() and resp["result"].HasMember("exception")) {
// const auto & exception = resp["result"]["exception"].GetString();
// log(LogLevel::Error, "rublon core exception %s", exception);
// return handleCoreException(exception);
// }
using namespace std::string_view_literals;
return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv;
}
bool isUnHealthy(const Document & coreResponse) const {
return coreResponse.HasParseError() or not coreResponse.HasMember("result");
}
protected:
HttpHandler http{};
public:
CoreHandler(const Configuration & config)
: secretKey{config.parameters.secretKey}, url{config.parameters.apiServer}, bypass{config.parameters.bypass}, http{} {}
: secretKey{config.secretKey.data()}, url{config.apiServer.data()}, bypass{config.bypass}, http{} {}
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
if(not responseSigned(response)) {
@ -53,14 +68,16 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
Document resp{&alloc};
resp.Parse(response.body.c_str());
if(resp.HasParseError() or not resp.HasMember("result")) {
log(LogLevel::Error, "rublon Core responded with broken data");
log(LogLevel::Debug, "Begin, Core Response validation");
if(isUnHealthy(resp)) {
log(LogLevel::Error, "Rublon Core responded with broken data");
return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}};
}
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("exception")) {
else if(containsException(resp)) {
const auto & exception = resp["result"]["exception"].GetString();
log(LogLevel::Error, "rublon core exception %s", exception);
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception);
return handleCoreException(exception);
}
@ -69,28 +86,14 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
if(exceptionString == "UserBypassedException" or exceptionString == "UserNotFoundException") {
return tl::unexpected{Error{PamBaypass{}}};
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}};
} else {
return tl::unexpected{
Error{CoreHandlerError{CoreHandlerError::CoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
}
}
tl::unexpected< Error > handleHttpError() const {
if(bypass) {
log(LogLevel::Warning, "User login bypass");
return tl::unexpected{Error{PamBaypass{}}};
} else {
log(LogLevel::Warning, "User login deny due to HTTP error");
return tl::unexpected{Error{PamDeny{}}};
}
}
tl::expected< Document, Error > handleError(const Error & error) const {
if(error.is< HttpError >() and error.hasClass(HttpError::Error)) {
return handleHttpError();
}
return tl::unexpected{Error{error}};
}
@ -105,20 +108,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
}
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
memory::StrictMonotonic_4k_HeapResource memoryResource;
memory::StrictMonotonic_8k_HeapResource memoryResource;
const auto validateSignature = [&](const auto & arg) { return this->validateSignature(arg); };
const auto validateResponse = [&](const auto & arg) { return this->validateResponse(mr, arg); };
const auto handleError = [&](const auto & error) { return this->handleError(error); };
const auto pmrs = [&](auto txt) { return std::pmr::string{txt, &memoryResource}; };
const auto pmrs = [&](const auto & txt) { return std::pmr::string{txt, &memoryResource}; };
Request request{&memoryResource};
Response response{&memoryResource};
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};

View File

@ -15,7 +15,5 @@ class CoreHandlerInterface {
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request");
return static_cast< const Impl * >(this)->request(mr, path, body);
}
// tl::expected< Document, Error >
};
} // namespace rublon

View File

@ -6,10 +6,8 @@
#include <curl/curl.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <memory_resource>
@ -75,8 +73,8 @@ class CURL {
public:
CURL() : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)} {}
tl::expected< std::reference_wrapper< Response >, HttpError >
tl::expected< std::reference_wrapper< Response >, ConnectionError >
request(std::string_view uri, const Request & request, Response & response) const {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
@ -105,7 +103,7 @@ class CURL {
if(res != CURLE_OK) {
log(LogLevel::Error, "%s no response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res));
return tl::unexpected{HttpError{HttpError::Timeout, 0}};
return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}};
}
long http_code = 0;
@ -113,7 +111,7 @@ class CURL {
if(http_code >= 500) {
log(LogLevel::Error, "%s response with code %d ", "CURL", http_code);
return tl::unexpected{HttpError{HttpError::Error, http_code}};
return tl::unexpected{ConnectionError{ConnectionError::Error, http_code}};
}
log(LogLevel::Debug, "Response:\n%s\n", response_data.c_str());

View File

@ -1,21 +1,27 @@
#pragma once
#include <rublon/non_owning_ptr.hpp>
#include <string>
#include <system_error>
#include <variant>
namespace rublon {
class NoError {
public:
static constexpr int errorClass = 0;
};
class HttpError {
#define names constexpr static inline const char * errorClassPrettyName[]
class ConnectionError {
public:
enum ErrorClass { Timeout, Error };
names = {"Timeout", "Error"};
constexpr HttpError() : errorClass{Timeout}, httpCode(200) {}
constexpr HttpError(ErrorClass e, long httpCode) : errorClass{e}, httpCode(httpCode) {}
constexpr static inline auto prettyName = "Connection Error";
constexpr ConnectionError() : errorClass{Timeout}, httpCode(200) {}
constexpr ConnectionError(ErrorClass e, long httpCode) : errorClass{e}, httpCode(httpCode) {}
constexpr const char * what() const {
return errorClassPrettyName[static_cast< int >(errorClass)];
}
ErrorClass errorClass;
long httpCode;
@ -23,20 +29,34 @@ class HttpError {
class CoreHandlerError {
public:
enum ErrorClass { Unknown, BadSigature, CoreException, BrokenData };
enum ErrorClass { BadSigature, CoreException, BrokenData };
names = {"BadSigature", "CoreException", "BrokenData"};
CoreHandlerError(ErrorClass e = Unknown) : errorClass{e} {}
constexpr static inline auto prettyName = "Core Handler Error";
CoreHandlerError(ErrorClass e = BadSigature) : errorClass{e} {}
CoreHandlerError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {}
constexpr const char * what() const {
return errorClassPrettyName[static_cast< int >(errorClass)];
}
ErrorClass errorClass;
std::string reson;
};
class MethodError {
public:
enum ErrorClass { BadMethod };
enum ErrorClass { BadMethod, BadUserInput, NoMethodAvailable };
names = {"BadMethod", "BadUserInput", "NoMethodAvailable"};
constexpr MethodError(ErrorClass e) : errorClass{e} {}
constexpr static inline auto prettyName = "Method Error";
constexpr MethodError(ErrorClass e = BadMethod) : errorClass{e} {}
constexpr const char * what() const {
return errorClassPrettyName[static_cast< int >(errorClass)];
}
ErrorClass errorClass;
};
@ -44,55 +64,50 @@ class MethodError {
class WerificationError {
public:
enum ErrorClass { WrongCode };
names = {"WrongCode"};
constexpr WerificationError(ErrorClass e) : errorClass{e} {}
constexpr static inline auto prettyName = "Werification Error";
constexpr WerificationError(ErrorClass e = WrongCode) : errorClass{e} {}
constexpr const char * what() const {
return errorClassPrettyName[static_cast< int >(errorClass)];
}
ErrorClass errorClass;
};
class Critical {
class RublonAuthenticationInterrupt {
public:
enum ErrorClass { Nok };
enum ErrorClass { UserBaypass, UserDenied, UserPending };
names = {"UserBaypass", "UserDenied", "UserPending"};
Critical(ErrorClass e = Nok) : errorClass{e} {}
constexpr static inline auto prettyName = "Rublon Authentication Interrupt";
ErrorClass errorClass;
};
RublonAuthenticationInterrupt(ErrorClass e = UserBaypass) : errorClass{e} {}
class PamBaypass {
public:
enum ErrorClass { Nok };
constexpr PamBaypass(ErrorClass e = Nok) : errorClass{e} {}
ErrorClass errorClass;
};
class PamDeny {
public:
enum ErrorClass { Nok };
constexpr PamDeny(ErrorClass e = Nok) : errorClass{e} {}
constexpr const char * what() const {
return errorClassPrettyName[static_cast< int >(errorClass)];
}
ErrorClass errorClass;
};
class Error {
using Error_t = std::variant< NoError, CoreHandlerError, HttpError, WerificationError, Critical, MethodError, PamBaypass, PamDeny >;
using Error_t =
std::variant< CoreHandlerError, ConnectionError, WerificationError, MethodError, RublonAuthenticationInterrupt >;
Error_t _error;
public:
enum Category { k_None, k_CoreHandlerError, k_SockerError, k_WerificationError, k_Critical, k_MethodError, k_PamBaypass, k_PamDeny };
enum Category { k_CoreHandlerError, k_ConnectionError, k_WerificationError, k_MethodError, k_RublonAuthenticationInterrupt };
Error() = default;
Error(CoreHandlerError error) : _error{error} {}
Error(HttpError error) : _error{error} {}
Error(ConnectionError error) : _error{error} {}
Error(MethodError error) : _error{error} {}
Error(WerificationError error) : _error{error} {}
Error(Critical error) : _error{error} {}
Error(PamBaypass error) : _error{error} {}
Error(PamDeny error) : _error{error} {}
Error(RublonAuthenticationInterrupt error) : _error{error} {}
Error(const Error &) = default;
Error(Error &&) = default;
@ -100,6 +115,7 @@ class Error {
Error & operator=(const Error &) = default;
Error & operator=(Error &&) = default;
public:
constexpr bool coreError() const {
return _error.index() == 1;
}
@ -112,10 +128,18 @@ class Error {
return static_cast< Category >(_error.index());
}
constexpr const char * categoryName() const noexcept {
return std::visit([](const auto & e) { return e.what(); }, _error);
}
constexpr int errorClass() const noexcept {
return std::visit([](const auto & e) { return static_cast< int >(e.errorClass); }, _error);
}
constexpr const char * errorClassName() const noexcept {
return std::visit([](const auto & e) { return e.prettyName; }, _error);
}
template < typename E >
constexpr bool is() const {
return category() == Error{E{}}.category();
@ -142,7 +166,7 @@ class Error {
}
};
constexpr bool operator==(const Error & e, const HttpError & socket) {
constexpr bool operator==(const Error & e, const ConnectionError & socket) {
return e.sockerError() && e.errorClass() == socket.errorClass;
}

View File

@ -8,44 +8,41 @@
#include <rublon/method/method_select.hpp>
#include <sys/utsname.h>
#include <regex>
namespace rublon {
class Verify {};
} // namespace rublon
namespace rublon {
std::pmr::string osName(std::pmr::memory_resource *mr) {
std::pmr::string osName(std::pmr::memory_resource * mr) {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
using Params = Configuration::Parameters;
Params configValues;
/// TODO move this to utils
std::ifstream file(std::filesystem::path{"/etc/os-release"});
if(not file.good())
return {"unknown", mr};
std::pmr::string line{&stackResource};
line.reserve(100);
/// TODO introduce toKeyValue function in utils
while(std::getline(file, line)) {
std::pmr::string _key{&stackResource};
std::pmr::string _value{&stackResource};
if(!line.length())
continue;
if(line[0] == '#' || line[0] == ';')
continue;
auto posEqual = line.find('=');
_key = line.substr(0, posEqual);
_value = line.substr(posEqual + 1);
if(_key == "PRETTY_NAME"){
_key = line.substr(0, posEqual);
_value = line.substr(posEqual + 1);
if(_key == "PRETTY_NAME") {
return {_value, mr};
}
}
return {"unknown", mr};
}
@ -55,12 +52,6 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
const char * apiPath = "/api/transaction/init";
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
const auto & rublonResponse = coreResponse["result"];
std::string tid = rublonResponse["tid"].GetString();
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]};
}
template < typename PamInfo_t >
void addPamInfo(Document & coreRequest, const PamInfo_t & pam) const {
auto & alloc = coreRequest.GetAllocator();
@ -73,32 +64,65 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
std::pmr::string releaseInfo{&stackResource};
auto & alloc = coreRequest.GetAllocator();
const auto os= osName(&stackResource);
if(os== "unknown") {
const auto os = osName(&stackResource);
if(os == "unknown") {
log(LogLevel::Warning, "No OS information available");
}
Value osNamePretty{os.data(), static_cast< unsigned >(os.size()), alloc};
Value ip{pam.ip().get(), alloc};
Value params{rapidjson::kObjectType};
params.AddMember("userIP", ip, alloc);
params.AddMember("appVer", "v.0.0.1", alloc); /// TODO add version to cmake
params.AddMember("os", osNamePretty, alloc);
coreRequest.AddMember("params", std::move(params), alloc);
}
template < typename PamInfo_t >
tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse,
const PamInfo_t & pam) const {
using namespace std::string_view_literals;
const auto & resp = coreResponse;
if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) {
const auto & status = resp["result"]["status"].GetString();
log(LogLevel::Warning, "Got enrolement message with stats %s", status);
if(status == "pending"sv and resp["result"].HasMember("webURI")) {
const auto & weburi = resp["result"]["webURI"].GetString();
pam.print("It seams that your account is not configured properly,\nplease contact your administrator for more information");
pam.print("also, please visit %s", weburi);
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}};
}
if(status == "denied"sv) {
pam.print("It seams that your account is disabled, please contact your administrator for more information");
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}};
}
}
/// TODO server response is malformed
return coreResponse;
}
tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const {
const auto & rublonResponse = coreResponse["result"];
std::string tid = rublonResponse["tid"].GetString();
return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]};
}
public:
const char * name = "Initialization";
Init(const rublon::Configuration & config) : base_t(config.parameters.systemToken, "") {}
Init(const rublon::Configuration & config) : base_t(config.systemToken.data(), "") {}
/// 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 createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); };
const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse, pam); };
RapidJSONPMRStackAlloc< 2048 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
@ -109,6 +133,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
return coreHandler
.request(alloc, apiPath, body) //
.and_then(checkEnrolement)
.and_then(createMethod);
}
};

View File

@ -0,0 +1,85 @@
#pragma once
#include <memory_resource>
namespace rublon {
namespace memory {
struct holder {
static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource();
};
inline void set_default_resource(std::pmr::memory_resource * memory_resource) {
holder{}._mr = memory_resource;
}
inline std::pmr::memory_resource * default_resource() {
return holder{}._mr;
}
template < std::size_t N >
class MonotonicStackResource : public std::pmr::monotonic_buffer_resource {
char _buffer[N];
public:
MonotonicStackResource() : std::pmr::monotonic_buffer_resource{_buffer, N, std::pmr::null_memory_resource()} {}
};
template < std::size_t N >
class UnsynchronizedStackResource : public std::pmr::unsynchronized_pool_resource {
MonotonicStackResource< N > _upstream;
public:
UnsynchronizedStackResource() : std::pmr::unsynchronized_pool_resource{&_upstream} {}
};
class MonotonicHeapResourceBase {
public:
std::pmr::memory_resource * _upstream{};
std::size_t _size{};
void * _buffer{nullptr};
MonotonicHeapResourceBase(std::size_t size) : _upstream{default_resource()}, _size{size}, _buffer{_upstream->allocate(size)} {}
~MonotonicHeapResourceBase() {
if(_buffer)
_upstream->deallocate(_buffer, _size);
}
};
template < std::size_t N >
class MonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
public:
MonotonicHeapResource()
: MonotonicHeapResourceBase{N}, std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, default_resource()} {}
};
template < std::size_t N >
class StrictMonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
public:
StrictMonotonicHeapResource()
: MonotonicHeapResourceBase{N},
std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, std::pmr::null_memory_resource()} {}
};
using StrictMonotonic_1k_HeapResource = StrictMonotonicHeapResource< 1 * 1024 >;
using StrictMonotonic_2k_HeapResource = StrictMonotonicHeapResource< 2 * 1024 >;
using StrictMonotonic_4k_HeapResource = StrictMonotonicHeapResource< 4 * 1024 >;
using StrictMonotonic_8k_HeapResource = StrictMonotonicHeapResource< 8 * 1024 >;
using Monotonic_1k_HeapResource = MonotonicHeapResource< 1 * 1024 >;
using Monotonic_2k_HeapResource = MonotonicHeapResource< 2 * 1024 >;
using Monotonic_4k_HeapResource = MonotonicHeapResource< 4 * 1024 >;
using Monotonic_8k_HeapResource = MonotonicHeapResource< 8 * 1024 >;
} // namespace memory
class RublonMemory {
public:
std::byte sharedMemory[32 * 1024] = {};
std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)};
std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr};
RublonMemory() {
std::pmr::set_default_resource(&rublonPoolResource);
}
};
} // namespace rublon

View File

@ -10,8 +10,8 @@
#include <rublon/pam_action.hpp>
#include <rublon/method/OTP.hpp>
#include <rublon/method/SMS.hpp>
#include <rublon/method/PUSH.hpp>
#include <rublon/method/SMS.hpp>
template < class F >
struct return_type;
@ -73,7 +73,7 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
return MethodProxy{method::OTP{this->_systemToken, std::move(tid)}};
} else if(_method == "sms") {
return MethodProxy{method::SMS{this->_systemToken, std::move(tid)}};
} else if(_method == "push"){
} else if(_method == "push") {
return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}};
}
@ -136,15 +136,15 @@ class MethodSelect {
memory::StrictMonotonic_2k_HeapResource memoryResource;
std::pmr::map< int, std::string > methods_id{&memoryResource};
pam.print("select method: ");
auto logMethodAvailable = [](auto &method){
auto logMethodAvailable = [](auto & method) { //
rublon::log(LogLevel::Debug, "Method %s found", method.c_str());
};
auto logMethodNotAvailable = [](auto &method){
auto logMethodNotAvailable = [](auto & method) {
rublon::log(LogLevel::Debug, "Method %s found, but has no handler", method.c_str());
};
auto print_methods = [&]() {
auto printAvailableMethods = [&]() -> tl::expected< int, MethodError > {
int i{};
for(const auto & method : _methods) {
if(method == "totp") {
@ -160,57 +160,75 @@ class MethodSelect {
methods_id[++i] = method;
continue;
}
if(method == "push" ){
if(method == "push") {
logMethodAvailable(method);
pam.print("%d: Mobile PUSH", i + 1);
methods_id[++i] = method;
continue;
}
logMethodNotAvailable(method);
}
if(i == 0) {
return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable));
}
return i;
};
const auto toMethodError = [&](conv::Error /*methodid*/) -> Error {
const auto toMethodError = [&](conv::Error /*methodid*/) -> MethodError {
pam.print("Input is not an number, please correct");
return MethodError{MethodError::BadMethod};
return MethodError{MethodError::BadUserInput};
};
const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, Error > {
const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, MethodError > {
auto hasMethod = methods_id.find(methodid) != methods_id.end();
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
return tl::unexpected{MethodError(MethodError::BadMethod)};
} else {
log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_id.at(methodid).c_str());
return PostMethod{_systemToken, _tid, methods_id.at(methodid)};
}
};
const auto askForMethodAgain = [&](const Error &) {
print_methods();
const auto askForMethodAgain = [&]() -> tl::expected< PostMethod, MethodError > {
printAvailableMethods();
return pam
.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){
const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > {
if(methods_number == 1) {
pam.print("Only one method available");
return 1;
}
return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size());
return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_number).transform_error(toMethodError);
};
print_methods();
return askForMethod() //
.transform_error(toMethodError)
const auto handleErrors = [&](const MethodError & e) -> tl::expected< PostMethod, MethodError > {
switch(e.errorClass) {
case MethodError::BadMethod: // User provided a number but the number if not found
return askForMethodAgain();
case MethodError::BadUserInput: // User provided id is invalid (NAN)
return askForMethodAgain();
case MethodError::NoMethodAvailable:
/// TODO print enrolement
default:
return tl::unexpected(e);
}
};
/// TODO make this some kind of global
const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; };
return printAvailableMethods() //
.and_then(askForMethod)
.and_then(createMethod)
.or_else(askForMethodAgain);
.or_else(handleErrors)
.map_error(toGenericError);
}
};

View File

@ -30,5 +30,4 @@ class NonOwningPtr {
return get();
}
};
} // namespace rublon

View File

@ -20,6 +20,10 @@ class AuthenticationStatus {
return false;
}
Action action() const {
return _action;
}
private:
Action _action;
};

View File

@ -16,26 +16,29 @@ namespace rublon {
// 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;
class RublonSession {
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);
// }
};
template < typename Pam_T, typename CoreHandler_T >
class RublonBase {
Pam_T & pam;
Configuration config{};
void initializeLogs() {
details::initLog();
}
public:
RublonBase(Pam_T & pam) : pam{pam} {}
// AuthenticationStatus authenticate() {}
};
using RublonLinux = RublonBase< LinuxPam, CoreHandler< CURL > >;
} // namespace rublon

View File

@ -1,10 +1,10 @@
#pragma once
#include <array>
#include <cstring>
#include <string_view>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <string_view>
namespace rublon {

View File

@ -19,10 +19,13 @@
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
#include <utility>
#include <rublon/memory.hpp>
namespace rublon {
inline bool fileGood(const std::filesystem::path & path) {
@ -44,74 +47,7 @@ inline bool readFile(const std::filesystem::path & path, T & destination) {
return true;
}
namespace memory {
struct holder {
static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource();
};
inline void set_default_resource(std::pmr::memory_resource * memory_resource) {
holder{}._mr = memory_resource;
}
inline std::pmr::memory_resource * default_resource() {
return holder{}._mr;
}
template < std::size_t N >
class MonotonicStackResource : public std::pmr::monotonic_buffer_resource {
char _buffer[N];
public:
MonotonicStackResource() : std::pmr::monotonic_buffer_resource{_buffer, N, std::pmr::null_memory_resource()} {}
};
template < std::size_t N >
class UnsynchronizedStackResource : public std::pmr::unsynchronized_pool_resource {
MonotonicStackResource< N > _upstream;
public:
UnsynchronizedStackResource() : std::pmr::unsynchronized_pool_resource{&_upstream} {}
};
class MonotonicHeapResourceBase {
public:
std::pmr::memory_resource * _upstream{};
std::size_t _size{};
void * _buffer{nullptr};
MonotonicHeapResourceBase(std::size_t size) : _upstream{default_resource()}, _size{size}, _buffer{_upstream->allocate(size)} {}
~MonotonicHeapResourceBase() {
if(_buffer)
_upstream->deallocate(_buffer, _size);
}
};
template < std::size_t N >
class MonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
public:
MonotonicHeapResource()
: MonotonicHeapResourceBase{N}, std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, default_resource()} {}
};
template < std::size_t N >
class StrictMonotonicHeapResource : MonotonicHeapResourceBase, public std::pmr::monotonic_buffer_resource {
public:
StrictMonotonicHeapResource()
: MonotonicHeapResourceBase{N},
std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, std::pmr::null_memory_resource()} {}
};
using StrictMonotonic_1k_HeapResource = StrictMonotonicHeapResource< 1 * 1024 >;
using StrictMonotonic_2k_HeapResource = StrictMonotonicHeapResource< 2 * 1024 >;
using StrictMonotonic_4k_HeapResource = StrictMonotonicHeapResource< 4 * 1024 >;
using StrictMonotonic_8k_HeapResource = StrictMonotonicHeapResource< 8 * 1024 >;
using Monotonic_1k_HeapResource = MonotonicHeapResource< 1 * 1024 >;
using Monotonic_2k_HeapResource = MonotonicHeapResource< 2 * 1024 >;
using Monotonic_4k_HeapResource = MonotonicHeapResource< 4 * 1024 >;
using Monotonic_8k_HeapResource = MonotonicHeapResource< 8 * 1024 >;
} // namespace memory
enum class LogLevel { Debug, Info, Warning, Error };
@ -129,14 +65,28 @@ constexpr bool syncLogFile = true;
namespace details {
constexpr const char * logPath() {
constexpr auto path = "/tmp/rublon-ssh.log";
constexpr auto path = "/var/log/rublon-ssh.log";
return path;
}
static void doLog(LogLevel level, const char * line) noexcept {
auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a"), fclose);
inline bool logExists() noexcept {
return std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "r"), fclose).get();
}
inline void initLog() noexcept {
if(not logExists()) {
auto fp = fopen(logPath(), "w");
if(fp) {
fclose(fp);
chmod(logPath(), S_IRUSR | S_IWUSR | S_IRGRP);
}
}
}
inline void doLog(LogLevel level, const char * line) noexcept {
auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a+"), fclose);
if(fp) {
///TODO add transaction ID
/// TODO add transaction ID
fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[( int ) level], line);
if(syncLogFile)
sync();
@ -160,16 +110,15 @@ void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept {
details::doLog(level, line.data());
}
class PrintUser{
class PrintUser {
public:
template < typename Printer >
PrintUser(Printer & p) : _impl{std::make_unique< ModelImpl >(p)} {}
private:
struct model{
struct model {
virtual ~model();
virtual void print(std::string_view line) const=0;
virtual void print(std::string_view line) const = 0;
};
template < typename Printer_T >
@ -181,20 +130,19 @@ class PrintUser{
Printer_T & _printer;
};
std::unique_ptr<model> _impl;
std::unique_ptr< model > _impl;
};
namespace conv{
namespace conv {
inline bool to_bool(std::string_view value) {
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 {
@ -205,7 +153,7 @@ namespace conv{
return tl::make_unexpected(Error::OutOfRange);
}
}
}
} // namespace conv
namespace details {
static inline std::string_view ltrim(std::string_view s) {

View File

@ -217,7 +217,7 @@ class WebSocket {
log(LogLevel::Debug, "start listening");
ws.connect("wss://staging-core.rublon.net/ws/socket.io/");
const auto attachToTransactionConfirmationChannel = [&](const auto & status) -> tl::expected< bool, bool > {
const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > {
/// TODO check status
auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create());
message->insert("channel", "transactionConfirmation." + _token);
@ -226,7 +226,7 @@ class WebSocket {
return true; /// TODO
};
const auto waitForUserAction = [&](const auto & connected) -> tl::expected< WebSocketSingleShotEventListener::Status, bool > {
const auto waitForUserAction = [&](const auto & /*connected*/) -> tl::expected< WebSocketSingleShotEventListener::Status, bool > {
WebSocketSingleShotEventListener eventListener{ws, _lock, _cond};
return eventListener.waitForEvent();
};

View File

@ -5,11 +5,7 @@
#include <security/pam_modules.h>
#include <syslog.h>
#include <rublon/init.hpp>
#include <rublon/json.hpp>
#include <rublon/pam.hpp>
#include <rublon/rublon.hpp>
#include <rublon/utils.hpp>
#define DLL_PUBLIC __attribute__((visibility("default")))
@ -30,13 +26,11 @@ DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh,
DLL_PUBLIC int
pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
using namespace rublon;
rublon::log(LogLevel::Debug, "flags: {%d}, argc: {%d}", flags, argc);
// std::freopen(rublon::details::logPath(), "a+", stdout);
// std::freopen(rublon::details::logPath(), "a+", stderr);
LinuxPam pam{pamh};
RublonLinux rublon{pam};
auto rublonConfig = ConfigurationFactory{}.systemConfig();
if(not rublonConfig.has_value()) {
pam.print("\n");
@ -44,11 +38,8 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
pam.print("\tcheck '%s' for more details\n", details::logPath());
return PAM_SUCCESS;
}
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);
RublonMemory mem;
CoreHandler CH{rublonConfig.value()};
@ -56,44 +47,69 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); };
auto confirmCode = [&](const MethodProxy & method) { return method.fire(CH, pam); };
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > {
if(status.userAuthorized()) {
rublon::log(rublon::LogLevel::Info, "Auth OK");
pam.print("RUBLON authentication SUCCESS!\n");
return PAM_SUCCESS;
} else {
rublon::log(rublon::LogLevel::Info, "User unauthorized");
pam.print("RUBLON authentication FAILED");
return PAM_MAXTRIES;
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
switch(status.action()) {
case AuthenticationStatus::Action::Bypass:
pam.print("\n\tRUBLON authentication bypased");
return PAM_SUCCESS;
case AuthenticationStatus::Action::Denied:
pam.print("\n\tRUBLON authentication FAILED");
return PAM_MAXTRIES;
case AuthenticationStatus::Action::Confirmed:
pam.print("\n\tRUBLON authentication SUCCESS");
return PAM_SUCCESS;
}
pam.print("RUBLON connector has exited with unknown code, access DENY!\n");
return PAM_MAXTRIES;
};
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > {
return printAuthMessageAndExit(status);
};
auto mapError = [&](const Error & error) -> tl::expected< int, Error > {
rublon::log(
LogLevel::Error, "auth problems due to %d class and %d category", error.errorClass(), static_cast< int >(error.category()));
if(error.is< PamBaypass >()) {
pam.print("\n RUBLON authentication bypased");
return PAM_SUCCESS;
log(LogLevel::Error, "Authorization interrupted with {%s::%s}", error.errorClassName(), error.categoryName());
if(error.is< RublonAuthenticationInterrupt >()) {
switch(error.get< RublonAuthenticationInterrupt >().errorClass) {
case RublonAuthenticationInterrupt::ErrorClass::UserBaypass:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
case RublonAuthenticationInterrupt::ErrorClass::UserDenied:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case RublonAuthenticationInterrupt::ErrorClass::UserPending:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
}
if(error.is< HttpError >()) {
pam.print("\n RUBLON server unavalible");
if(rublonConfig->parameters.bypass) {
pam.print("\n RUBLON authentication bypased");
return PAM_SUCCESS;
if(error.is< MethodError >()) {
switch(error.get< MethodError >().errorClass) {
case MethodError::ErrorClass::BadMethod:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case MethodError::ErrorClass::BadUserInput:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case MethodError::ErrorClass::NoMethodAvailable:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
}
if(error.is< ConnectionError >()) {
if(rublonConfig->bypass) {
pam.print("Connection Error, bypass enabled");
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
} else {
pam.print("RUBLON authentication FAILED");
return PAM_MAXTRIES;
pam.print("Connection Error, bypass disabled");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}
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");
return PAM_MAXTRIES;
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
};
auto ret = Init{rublonConfig.value()}

View File

@ -9,97 +9,81 @@
#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; }
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);
}
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(ConnectionError{})));
}
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(std::string{conf.apiServer.data()} + "/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(RublonAuthenticationInterrupt{})));
}
TEST_F(CoreHandlerTests, onSuccessfullMessageCoreShouldByFine) {
EXPECT_CALL(http, request(_, _))
.WillOnce(
Return(RawHttpResponse{_res}.authenticationSuccessfullMessage()));
EXPECT_THAT(sut.request("/path", doc), //
AllOf(HasValue()));
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(RublonAuthenticationInterrupt{})));
}

View File

@ -21,11 +21,11 @@ MATCHER(IsObject, "") {
}
MATCHER(IsPAMBaypass,""){
return arg.error().category() == rublon::Error::k_PamBaypass;
return arg.error().category() == rublon::Error::k_RublonAuthenticationInterrupt;
}
MATCHER(IsPAMDeny,""){
return arg.error().category() == rublon::Error::k_PamDeny;
return arg.error().category() == rublon::Error::k_RublonAuthenticationInterrupt;
}
MATCHER_P(HasKey, key, "") {

View File

@ -12,20 +12,20 @@
#include "rublon/sign.hpp"
namespace {
rublon::Configuration conf{rublon::Configuration::Parameters{//
"320BAB778C4D4262B54CD243CDEFFAFD",
"39e8d771d83a2ed3cc728811911c25",
"https://staging-core.rublon.net",
rublon::Configuration conf{rublon::Configuration{//
{"320BAB778C4D4262B54CD243CDEFFAFD"},
{"39e8d771d83a2ed3cc728811911c25"},
{"https://staging-core.rublon.net"},
1,
true,
true,
false,
false,
true}};
rublon::Configuration confBypass{rublon::Configuration::Parameters{//
"320BAB778C4D4262B54CD243CDEFFAFD",
"39e8d771d83a2ed3cc728811911c25",
"https://staging-core.rublon.net",
rublon::Configuration confBypass{rublon::Configuration{//
{"320BAB778C4D4262B54CD243CDEFFAFD"},
{"39e8d771d83a2ed3cc728811911c25"},
{"https://staging-core.rublon.net"},
1,
true,
true,
@ -63,8 +63,8 @@ struct ResponseMockOptions {
std::string coreException{};
std::set< std::string > methods{};
rublon::Error error;
std::optional<rublon::Error> error;
};
class InitCoreResponse {
@ -186,12 +186,12 @@ class ResponseBase {
}
Generator & withTimeoutError() {
options.error = rublon::Error{rublon::HttpError{}};
options.error = rublon::Error{rublon::ConnectionError{}};
return static_cast< Generator & >(*this);
}
Generator & withServiceUnavailableError() {
options.error = rublon::Error{rublon::HttpError{rublon::HttpError::Error, 405}};
options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::Error, 405}};
return static_cast< Generator & >(*this);
}
@ -212,11 +212,11 @@ class ResponseBase {
}
operator auto() {
return options.error.category() == rublon::Error::k_None ? static_cast< Generator * >(this)->generate() : tl::unexpected{error()};
return not options.error.has_value() ? static_cast< Generator * >(this)->generate() : tl::unexpected{error()};
}
rublon::Error error() {
return rublon::Error{options.error};
return rublon::Error{options.error.value()};
}
std::variant< InitCoreResponse, MethodSelectCoreResponse, CodeVerificationResponse, AuthenticationOkResponse > _coreGenerator;
@ -245,7 +245,7 @@ class RawHttpResponse : public ResponseBase< RawHttpResponse > {
};
static auto signResponse(rublon::Response & res, ResponseMockOptions opts) {
const auto & sign =
opts.generateBrokenSigneture ? std::array< char, 65 >{} : rublon::signData(res.body, conf.parameters.secretKey.c_str());
opts.generateBrokenSigneture ? std::array< char, 65 >{} : rublon::signData(res.body, conf.secretKey.data());
res.headers["x-rublon-signature"] = sign.data();
}
tl::expected< std::reference_wrapper< rublon::Response >, rublon::Error > generate() {

View File

@ -1,10 +1,10 @@
#!/bin/bash
if [ ! -f /etc/rublon/rublon.config ]
if [ ! -f /etc/rublon.config ]
then
mkdir -p /etc/rublon
cp -a /usr/share/rublon/rublon.config.defaults /etc/rublon/rublon.config
cp -a /usr/share/rublon/rublon.config.defaults /etc/rublon.config
fi
# get system id
@ -23,5 +23,5 @@ grep -qe "^UsePAM" $SSHD_CONF && \
sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || \
echo "UsePAM yes" >> $SSHD_CONF
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aauth required pam_rublon.so' $SSHD_PAM_CONF
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aaccount required pam_rublon.so' $SSHD_PAM_CONF
grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF
grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF