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:
parent
c3127e8b58
commit
8ffa20fffa
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 >;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
19
PAM/ssh/include/rublon/method/SmsLink.hpp
Normal file
19
PAM/ssh/include/rublon/method/SmsLink.hpp
Normal 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
|
||||
19
PAM/ssh/include/rublon/method/YOTP.hpp
Normal file
19
PAM/ssh/include/rublon/method/YOTP.hpp
Normal 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
|
||||
@ -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() //
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
|
||||
namespace rublon {
|
||||
|
||||
enum class PamAction { accept, decline };
|
||||
|
||||
enum Authen {};
|
||||
|
||||
class AuthenticationStatus {
|
||||
public:
|
||||
enum class Action { Denied, Confirmed, Bypass };
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user