Bwi/sms link (#8)

* generate user enrolement message

* cleanup

* Fix bugs found during testing

* Add yotp message [not verified]

* smsLink implementation

* implement SMS Link

* YOTP fixes

* Add SMS link
This commit is contained in:
rublon-bwi 2024-02-13 16:50:45 +01:00 committed by GitHub
parent c3127e8b58
commit 8ffa20fffa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 290 additions and 133 deletions

View File

@ -8,9 +8,14 @@
#include <rublon/curl.hpp>
#include <rublon/json.hpp>
#include <rublon/sign.hpp>
#include <rublon/websockets.hpp>
#include <rublon/pam_action.hpp>
#include <tl/expected.hpp>
#include <rublon/core_handler_interface.hpp>
namespace rublon {
template < typename HttpHandler = CURL >
@ -33,13 +38,6 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
}
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;
}
@ -57,7 +55,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
if(not responseSigned(response)) {
log(LogLevel::Error, "CoreHandlerError::BadSigature");
log(LogLevel::Error, "rublon core response is not signed");
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
}
@ -74,11 +72,11 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
log(LogLevel::Error, "Rublon Core responded with broken data");
return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}};
}
else if(containsException(resp)) {
const auto & exception = resp["result"]["exception"].GetString();
const auto* exception = JSONPointer{"/result/exception", &alloc}.Get(resp);
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception);
return handleCoreException(exception);
return handleCoreException(exception->GetString());
}
return resp;
@ -89,7 +87,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}};
} else {
return tl::unexpected{
Error{CoreHandlerError{CoreHandlerError::CoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
}
}
@ -130,6 +128,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
.and_then(validateResponse)
.or_else(handleError);
}
///TODO use WS_Handler type to allow mocking / changind implementation of WS
tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const {
bool aproved{};
WebSocket ws;
aproved = ws.connect(url, tid);
log(LogLevel::Info, "websocket :s", aproved ? "approved" : "denied");
return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} :
AuthenticationStatus{AuthenticationStatus::Action::Denied};
}
};
} // namespace rublon

View File

@ -5,6 +5,7 @@
#include <rublon/error.hpp>
#include <rublon/json.hpp>
#include <rublon/utils.hpp>
#include <rublon/pam_action.hpp>
namespace rublon {
@ -15,5 +16,10 @@ class CoreHandlerInterface {
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request");
return static_cast< const Impl * >(this)->request(mr, path, body);
}
tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const {
rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen");
return static_cast< const Impl * >(this)->waitForConfirmation(tid);
}
};
} // namespace rublon

View File

@ -6,8 +6,10 @@
#include <curl/curl.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <memory_resource>
@ -79,7 +81,7 @@ class CURL {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
std::pmr::string response_data{&stackResource};
response_data.reserve(6000);
response_data.reserve(7000);
/// 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);
@ -98,7 +100,13 @@ 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, "Sending request to %s", uri.data());
for(const auto &[name, value] : request.headers){
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
}
log(LogLevel::Debug, "Body %s", request.body.c_str());
const auto res = curl_easy_perform(curl.get());
if(res != CURLE_OK) {
@ -111,16 +119,20 @@ class CURL {
if(http_code >= 500) {
log(LogLevel::Error, "%s response with code %d ", "CURL", http_code);
return tl::unexpected{ConnectionError{ConnectionError::Error, http_code}};
return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}};
}
log(LogLevel::Debug, "Response:\n%s\n", response_data.c_str());
long size;
long size{};
curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size);
details::headers(response_data, response.headers);
response.body = response_data.substr(size);
log(LogLevel::Debug, "Received %d bytes", response_data.size());
for(const auto &[name, value] : response.headers){
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
}
log(LogLevel::Debug, "Body %s", response.body.c_str());
return response;
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <rublon/non_owning_ptr.hpp>
#include <tl/expected.hpp>
#include <string>
#include <variant>
@ -9,9 +10,10 @@ namespace rublon {
#define names constexpr static inline const char * errorClassPrettyName[]
class ConnectionError {
public:
enum ErrorClass { Timeout, Error };
enum ErrorClass { Timeout, HttpError };
names = {"Timeout", "Error"};
constexpr static inline auto prettyName = "Connection Error";
@ -29,8 +31,8 @@ class ConnectionError {
class CoreHandlerError {
public:
enum ErrorClass { BadSigature, CoreException, BrokenData };
names = {"BadSigature", "CoreException", "BrokenData"};
enum ErrorClass { BadSigature, RublonCoreException, BrokenData };
names = {"BadSigature", "RublonCoreException", "BrokenData"};
constexpr static inline auto prettyName = "Core Handler Error";

View File

@ -13,36 +13,35 @@ 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;
/// 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};
}
@ -52,6 +51,12 @@ 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();
@ -86,7 +91,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
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);
@ -102,16 +107,8 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > {
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"]};
return coreResponse;
}
public:

View File

@ -60,7 +60,8 @@ namespace memory {
: MonotonicHeapResourceBase{N},
std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, std::pmr::null_memory_resource()} {}
};
using StrictMonotonic_512_HeapResource = StrictMonotonicHeapResource< 512 >;
using StrictMonotonic_1k_HeapResource = StrictMonotonicHeapResource< 1 * 1024 >;
using StrictMonotonic_2k_HeapResource = StrictMonotonicHeapResource< 2 * 1024 >;
using StrictMonotonic_4k_HeapResource = StrictMonotonicHeapResource< 4 * 1024 >;

View File

@ -13,7 +13,7 @@ namespace rublon::method {
class OTP : public PasscodeBasedAuth {
public:
OTP(std::string systemToken, std::string tid)
: PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ") {}
: PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ", 6, true) {}
};
} // namespace rublon::method

View File

@ -12,7 +12,8 @@ namespace rublon::method {
class PUSH : public WebsocketBasedAuth {
public:
PUSH(std::string systemToken, std::string tid) : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {}
PUSH(std::string systemToken, std::string tid)
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {}
};
} // namespace rublon::method

View File

@ -12,7 +12,7 @@ namespace rublon::method {
class SMS : public PasscodeBasedAuth {
public:
SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ") {}
SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ", 6, true) {}
};
} // namespace rublon::method

View File

@ -0,0 +1,19 @@
#pragma once
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
#include <rublon/method/websocket_based_auth.hpp>
namespace rublon::method {
class SmsLink : public WebsocketBasedAuth {
public:
SmsLink(std::string systemToken, std::string tid)
: WebsocketBasedAuth(std::move(systemToken), std::move(tid), "smsLink") {}
};
} // namespace rublon::method

View File

@ -0,0 +1,19 @@
#pragma once
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
#include <rublon/method/passcode_based_auth.hpp>
namespace rublon::method {
class YOTP : public PasscodeBasedAuth {
public:
YOTP(std::string systemToken, std::string tid)
: PasscodeBasedAuth(std::move(systemToken), std::move(tid), "YOTP", "Press Yubikey: ", 44, false) {}
};
} // namespace rublon::method

View File

@ -12,6 +12,8 @@
#include <rublon/method/OTP.hpp>
#include <rublon/method/PUSH.hpp>
#include <rublon/method/SMS.hpp>
#include <rublon/method/SmsLink.hpp>
#include <rublon/method/YOTP.hpp>
template < class F >
struct return_type;
@ -56,7 +58,7 @@ class MethodProxy {
}
private:
std::variant< method::OTP, method::SMS, method::PUSH > _impl;
std::variant< method::OTP, method::SMS, method::PUSH, method::SmsLink, method::YOTP > _impl;
};
class PostMethod : public rublon::AuthenticationStep< PostMethod > {
@ -73,8 +75,12 @@ 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)}};
} else if(_method == "smsLink") {
return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}};
} else if(_method == "yotp") {
return MethodProxy{method::YOTP{this->_systemToken, std::move(tid)}};
}
else
@ -83,10 +89,7 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > {
void addParams(Document & body) const {
auto & alloc = body.GetAllocator();
body.AddMember("method", Value{_method.c_str(), alloc}, alloc);
body.AddMember("GDPRAccepted", "true", alloc);
body.AddMember("tosAccepted", "true", alloc);
}
public:
@ -153,7 +156,15 @@ class MethodSelect {
methods_id[++i] = method;
continue;
}
/// Needs changes in Core to work
// if(method == "yotp") {
// logMethodAvailable(method);
// pam.print("%d: Yubikey", i + 1);
// methods_id[++i] = method;
// continue;
// }
if(method == "sms") {
logMethodAvailable(method);
pam.print("%d: SMS code", i + 1);
@ -167,6 +178,14 @@ class MethodSelect {
methods_id[++i] = method;
continue;
}
if(method == "smsLink") {
logMethodAvailable(method);
pam.print("%d: SMS Link", i + 1);
methods_id[++i] = method;
continue;
}
logMethodNotAvailable(method);
}
@ -198,6 +217,7 @@ class MethodSelect {
.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) //
.transform_error(toMethodError)
.and_then(createMethod);
/// TODO or_else(printErrorAndDenyAccess); ??
};
const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > {
@ -221,7 +241,7 @@ class MethodSelect {
}
};
/// TODO make this some kind of global
const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; };
return printAvailableMethods() //

View File

@ -9,20 +9,40 @@
namespace rublon::method {
class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
protected:
using base_t = AuthenticationStep< PasscodeBasedAuth >;
const char * uri = "/api/transaction/confirmCode";
const char * userMessage{nullptr};
const uint_fast8_t length;
const bool onlyDigits;
constexpr static bool isdigit(char ch) {
return std::isdigit(static_cast< unsigned char >(ch));
}
static bool digitsOnly(std::string_view userinput) {
bool digitsOnly(std::string_view userinput) const {
return std::all_of(userinput.cbegin(), userinput.cend(), isdigit);
}
static bool hasProperLength(std::string_view userInput) {
return userInput.size() == 6;
bool hasValidLength(std::string_view userInput) const {
if(userInput.size() == length) {
log(LogLevel::Debug, "User input size %d is correct", userInput.size());
return true;
} else {
log(LogLevel::Warning, "User input size %d is different then %d", userInput.size(), length);
return false;
}
}
bool hasValidCharacters(std::string_view userInput) const {
if(onlyDigits ? digitsOnly(userInput) : true) {
log(LogLevel::Debug, "User input contains valid characters");
return true;
} else {
log(LogLevel::Warning, "User input contains characters different then digits");
return false;
}
}
template < typename PamInfo_t = LinuxPam >
@ -30,7 +50,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
auto & alloc = body.GetAllocator();
auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage);
if(hasProperLength(vericode) and digitsOnly(vericode)) {
if(hasValidLength(vericode) and hasValidCharacters(vericode)) {
body.AddMember("vericode", Value{vericode.c_str(), alloc}, alloc);
return body;
}
@ -40,7 +60,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
template < typename PamInfo_t = LinuxPam >
tl::expected< std::reference_wrapper< Document >, Error > askForPasscodeAgain(Document & body, const PamInfo_t & pam) const {
pam.print("passcode has wrong number of digits or illegal characters, please correct");
pam.print("passcode has wrong number of digits or contains illegal characters, please correct");
return readPasscode(body, pam);
}
@ -53,7 +73,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
pam.print("Wrong code");
return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}};
}
pam.print("Verification code validated");
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
}
@ -61,8 +81,13 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > {
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} {}
PasscodeBasedAuth(std::string systemToken,
std::string tid,
const char * name,
const char * userMessage,
uint_fast8_t length,
bool numbersOnly)
: base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, length{length}, onlyDigits{numbersOnly}, 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 {

View File

@ -3,6 +3,7 @@
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/configuration.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
@ -15,21 +16,16 @@ class WebsocketBasedAuth : public AuthenticationStep< WebsocketBasedAuth > {
public:
const char * name = "";
WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name)
: base_t(std::move(systemToken), std::move(tid)), 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 {
tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
log(LogLevel::Info, "starting WS");
WebSocket ws;
pam.print("Waiting for user approval");
auto aproved = ws.connect(this->_tid);
return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} :
AuthenticationStatus{AuthenticationStatus::Action::Denied};
pam.print("Waiting for approval");
return coreHandler.waitForConfirmation(_tid);
}
};

View File

@ -2,10 +2,6 @@
namespace rublon {
enum class PamAction { accept, decline };
enum Authen {};
class AuthenticationStatus {
public:
enum class Action { Denied, Confirmed, Bypass };

View File

@ -18,27 +18,55 @@ namespace rublon {
// }
// }
class RublonSession {
public:
std::pmr::string _tid;
RublonSession() {
/// create memoryResource
///
}
void startTransaction(std::string_view tid) {}
};
class RublonExceptionHandler{
public:
};
template < typename Pam_T, typename CoreHandler_T >
class RublonBase {
Pam_T & pam;
Configuration config{};
const Pam_T & _pam;
Configuration _config{};
void initializeLogs() {
details::initLog();
}
public:
RublonBase(Pam_T & pam) : pam{pam} {}
RublonBase(const Pam_T & pam, Configuration config) : _pam{pam}, _config{config} {}
// AuthenticationStatus authenticate() {}
AuthenticationStatus authenticate() const {
}
};
using RublonLinux = RublonBase< LinuxPam, CoreHandler< CURL > >;
class RublonFactory{
public:
template <typename Pam_t>
tl::expected<RublonLinux, bool> create(const Pam_t &pam){
auto config = ConfigurationFactory{}.systemConfig();
if(not config.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());
return tl::unexpected{false};
}
return RublonLinux{pam, *config};
}
};
} // namespace rublon

View File

@ -6,10 +6,8 @@
#include <condition_variable>
#include <mutex>
#include <thread>
namespace rublon {
#define SERVER_ADDRESS "wss://staging-core.rublon.net/ws/socket.io"
#define TIMEOUT_SECONDS 90
/// {"status":"ERROR","code":400,"result":{"exception":"MobileAppMissingException","code":32,"errorMessage":"No mobile app
@ -86,14 +84,15 @@ class WebSocketSingleShotEventListener {
bool event_received = false;
Status _status{};
void printobj (std::size_t i, const std::map< std::string, sio::message::ptr > & objects) {
for(const auto &[name, msg]:objects){
void printobj(std::size_t i, const std::map< std::string, sio::message::ptr > & objects) {
for(const auto & [name, msg] : objects) {
log(LogLevel::Debug, "Object %ld: %s", i, name.c_str());
printMessage(i, msg);
}
};
void printMessage (std::size_t i, const sio::message::ptr & message) {
void printMessage(std::size_t i, const sio::message::ptr & message) {
switch(message->get_flag()) {
case sio::message::flag_integer:
log(LogLevel::Debug, "message %ld integer : %d", i, message->get_int());
@ -134,7 +133,6 @@ class WebSocketSingleShotEventListener {
log(LogLevel::Debug, "event nsp : %s", e.get_nsp().c_str());
log(LogLevel::Debug, "event messages: %d", e.get_messages().size());
for(std::size_t i = 0; i < e.get_messages().size(); i++) {
const auto & message = e.get_messages().at(i);
printMessage(i, message);
@ -145,13 +143,12 @@ class WebSocketSingleShotEventListener {
void onConfirmed(sio::event & e) {
generic(e, Status::Approved);
log(LogLevel::Info, "Autentication confirmed over PUSH");
log(LogLevel::Info, "Autentication confirmed");
}
void onDenied(sio::event & e) {
generic(e, Status::Denied);
log(LogLevel::Info, "Autentication denied by user");
sleep(30);
}
void onExpired(sio::event & e) {
@ -169,7 +166,7 @@ class WebSocketSingleShotEventListener {
void onAny(sio::event & e) {
std::lock_guard lock{_lock};
event_received = true;
log(LogLevel::Info, "name %s", e.get_name().c_str());
log(LogLevel::Info, "onAny message received: name %s", e.get_name().c_str());
_cond.notify_all();
}
@ -184,12 +181,15 @@ class WebSocketSingleShotEventListener {
_ws.socket()->on_any([this](sio::event & e) { this->onAny(e); });
}
/// TODO pass timeout
tl::expected< Status, bool > waitForEvent() {
std::lock_guard lock{_lock};
if(!event_received) {
log(LogLevel::Info, "Waiting for confirmation");
log(LogLevel::Info, "Waiting for WS event");
if(_cond.wait_until(_lock, std::chrono::system_clock::now() + std::chrono::minutes{2}) == std::cv_status::timeout) {
log(LogLevel::Info, "Waiting for confirmation failed due to WS timeout");
log(LogLevel::Info, "Waiting for WS event failed due to WS timeout");
}else{
log(LogLevel::Info, "Event arrived");
}
}
@ -198,29 +198,38 @@ class WebSocketSingleShotEventListener {
};
class WebSocket {
sio::client ws;
public:
std::string token;
WebSocket() {}
~WebSocket() {}
bool connect(std::string _token) {
bool connect(std::string_view uri, std::string_view tid) {
std::mutex _lock;
std::condition_variable_any _cond;
sio::client ws;
/// needed here only for rublon core api URL, so 1k fine
memory::StrictMonotonic_1k_HeapResource memoryResource;
std::pmr::string str{uri.data(), uri.size(), &memoryResource};
std::pmr::string httpsPrefix = {"https://", &memoryResource};
token = _token;
if(uri.rfind(httpsPrefix, 0) == 0) {
str.replace(str.find(httpsPrefix), httpsPrefix.size(), "wss://");
}
str += "/ws/socket.io/";
// ws.set_logs_verbose();
log(LogLevel::Debug, "start listening");
ws.connect("wss://staging-core.rublon.net/ws/socket.io/");
log(LogLevel::Debug, "WS connect to %s", str.c_str());
ws.connect(str.c_str());
const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > {
memory::StrictMonotonic_1k_HeapResource resource;
std::pmr::string channel{&resource};
channel.reserve(100);
channel += "transactionConfirmation.";
channel += tid;
/// TODO check status
auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create());
message->insert("channel", "transactionConfirmation." + _token);
message->insert("channel", channel.c_str());
log(LogLevel::Debug, "emiting %s message on subscribe channel", channel.c_str());
ws.socket()->emit("subscribe", {message});
return true; /// TODO
@ -231,15 +240,13 @@ class WebSocket {
return eventListener.waitForEvent();
};
WebSocketConnectionListener l(ws, _lock, _cond);
auto v = l.waitForConnection() //
auto v = WebSocketConnectionListener{ws, _lock, _cond}
.waitForConnection() //
.and_then(attachToTransactionConfirmationChannel)
.and_then(waitForUserAction)
.value_or(WebSocketSingleShotEventListener::Status::Denied);
return v == WebSocketSingleShotEventListener::Status::Approved;
}
void listen() {}
};
} // namespace rublon

View File

@ -26,22 +26,23 @@ 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;
// std::freopen(rublon::details::logPath(), "a+", stdout);
LinuxPam pam{pamh};
RublonLinux rublon{pam};
auto rublonConfig = ConfigurationFactory{}.systemConfig();
if(not rublonConfig.has_value()) {
// std::freopen(rublon::details::logPath(), "a+", stderr);
auto config = ConfigurationFactory{}.systemConfig();
if(not config.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());
return PAM_SUCCESS;
}
RublonMemory mem;
CoreHandler CH{rublonConfig.value()};
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);
CoreHandler CH{*config};
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); };
@ -96,7 +97,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
if(error.is< ConnectionError >()) {
if(rublonConfig->bypass) {
if(config->bypass) {
pam.print("Connection Error, bypass enabled");
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
} else {
@ -106,13 +107,17 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
}
if(error.is< CoreHandlerError >()) {
pam.print("\n RUBLON server returned '%s' exception", error.get< CoreHandlerError >().reson.c_str());
const auto &reson = error.get< CoreHandlerError >().reson;
pam.print("\n RUBLON server returned '%s' exception", reson.c_str());
if(reson == "UserBypassedException" or reson == "UserNotFoundException")
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
};
auto ret = Init{rublonConfig.value()}
auto ret = Init{config.value()}
.fire(CH, pam) //
.and_then(selectMethod)
.and_then(confirmMethod)

View File

@ -5,9 +5,6 @@
#include <rublon/error.hpp>
#include <rublon/json.hpp>
#include <algorithm>
#include <array>
#include <set>
#include <string>
namespace io {
@ -47,4 +44,6 @@ static constexpr const char * wrongPasscode = //
static constexpr const char * projectError =
R"json({"status":"ERROR","code":400,"result":{"exception":"APIException","code":10,"errorMessage":"Project error","details":null}})json";
static constexpr const char * blah= "";
} // namespace

View File

@ -191,7 +191,7 @@ class ResponseBase {
}
Generator & withServiceUnavailableError() {
options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::Error, 405}};
options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::HttpError, 405}};
return static_cast< Generator & >(*this);
}

View File

@ -10,9 +10,11 @@
using namespace testing;
using namespace rublon;
///TODO test if lenght is checked
class PasscodeBasedAuthTest : public Test {
public:
PasscodeBasedAuthTest() : sut{systemToken, tid, name, userMessage} {}
PasscodeBasedAuthTest() : sut{systemToken, tid, name, userMessage, 6, true} {}
std::string systemToken, tid;
const char * name = "Test";

View File

@ -1,17 +1,20 @@
#!/bin/bash
SSHD_CONF=/etc/ssh/sshd_config
SSHD_PAM_CONF=/etc/pam.d/sshd
RUBLON_CONFIG=/etc/rublon.config
if [ ! -f /etc/rublon.config ]
then
mkdir -p /etc/rublon
cp -a /usr/share/rublon/rublon.config.defaults /etc/rublon.config
cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG
chown root:root $RUBLON_CONFIG
chmod 640 $RUBLON_CONFIG
fi
# get system id
. /etc/os-release
SSHD_CONF=/etc/ssh/sshd_config
SSHD_PAM_CONF=/etc/pam.d/sshd
grep -qe "^PasswordAuthentication" $SSHD_CONF && \
sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \
@ -25,3 +28,11 @@ grep -qe "^UsePAM" $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
# ### UBUNTU 22.04 notes
# on ubuntu 22.04 options
# PasswordAuthentication yes
# ChallengeResponseAuthentication yes
# need to be set in configuration file AFTER UsePAM yes
# it will fail to use PAM (and will lock the machine) it this is not true