rublon-ssh/PAM/ssh/include/rublon/core_handler.hpp
rublon-bwi 8ffa20fffa
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
2024-02-13 16:50:45 +01:00

147 lines
5.5 KiB
C++

#pragma once
#include <memory_resource>
#include <string>
#include <rublon/configuration.hpp>
#include <rublon/core_handler_interface.hpp>
#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 >
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
std::string secretKey;
std::string url;
bool bypass;
void signRequest(Request & request) const {
request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, secretKey).data(), request.headers.get_allocator()};
}
bool responseSigned(const Response & response) const {
const auto & xRubResp = response.headers.at("x-rublon-signature");
const auto & sign = signData(response.body, secretKey);
const bool signatureMatch = xRubResp == sign.data();
if(not signatureMatch)
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
return signatureMatch;
}
bool containsException(const Document & coreResponse) const {
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.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)) {
log(LogLevel::Error, "rublon core response is not signed");
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
}
return response;
}
tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const {
Document resp{&alloc};
resp.Parse(response.body.c_str());
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}};
}
else if(containsException(resp)) {
const auto* exception = JSONPointer{"/result/exception", &alloc}.Get(resp);
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception);
return handleCoreException(exception->GetString());
}
return resp;
}
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
if(exceptionString == "UserBypassedException" or exceptionString == "UserNotFoundException") {
return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}};
} else {
return tl::unexpected{
Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}};
}
}
tl::expected< Document, Error > handleError(const Error & error) const {
return tl::unexpected{Error{error}};
}
template < typename T >
static void stringifyTo(const Document & body, T & to) {
memory::Monotonic_2k_HeapResource tmpResource;
RapidJSONPMRAlloc alloc{&tmpResource};
StringBuffer jsonStr{&alloc};
Writer writer{jsonStr, &alloc};
body.Accept(writer);
to = jsonStr.GetString();
}
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
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 = [&](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};
return http
.request(uri, request, response) //
.and_then(validateSignature)
.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