rublon-ssh/PAM/ssh/include/rublon/core_handler.hpp
rublon-bwi 9415174eba
Bwi/bugfix round2 (#9)
* Fix log file access, refactor configuration reading class

* Remove bypass option in favor of failmode

* fix loging, print enrolment info

* Add EMAIL method

* Add yubi authentication method

* Add support for verification message

* Add verification

* Made changes in Vagrant's files to run different OSs

* Switch off tests and packages demands to run PAM on Debian 11

* Add authentication totp

* Changes in utils

* Remove unnessesary interface

* Changed vagrant files and postinstal script for Ubuntu 20 and 22

* Moved adding PasswordAuth to vagrant file from posinst

* Added ubuntu 24.04

* Set version

* Poprawki UI

* WebSocket implementation 

* Add totp authentication method

* fixup changes in utils

* Remove unnessesary interface and simplify code

* Remove "default" message handler from WebSocket class

* Change display names of known authentication methods

* Cleanup code in 'main' file

* Add CheckApplication

* Remove unused function

* Changed vagrant files and postinstal script for Ubuntu 20 and 22

* Moved adding PasswordAuth to vagrant file from posinst

* Added ubuntu 24.04

* Set version to 2.0.2

* Proper handle for missing configuration

* Fixup use value of optional object

* Add more vCPU/RAM to vagrant VM's + fix translations

* Minor WS fixes, translations

* Proper handler for Werification error

* Make use of prompt parameter

* Add max number of prompts

* remove unused code, fir includes

* Add Waiting status

* Add check application status check

---------

Co-authored-by: Madzik <m.w@linux.pl>
2024-05-28 12:04:20 +02:00

174 lines
6.6 KiB
C++
Executable File

#pragma once
#include <memory>
#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/pam_action.hpp>
#include <rublon/sign.hpp>
#include <rublon/utils.hpp>
#include <rublon/websockets.hpp>
#include <tl/expected.hpp>
namespace rublon {
template < typename HttpHandler = CURL >
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
std::string secretKey;
std::string rublonCore;
mutable std::unique_ptr< WebSocket > _ws;
void signRequest(Request & request) const {
request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, secretKey).data(), request.headers.get_allocator()};
}
bool hasSignature(const Response & response) const {
if(not response.headers.count("x-rublon-signature")) {
log(LogLevel::Error, "No x-rublon-signature");
return false;
}
return true;
}
// bool hasRateLimit(const Response & response)const {
// if(not response.headers.count("x-ratelimit-remaining")) {
// log(LogLevel::Error, "No x-ratelimit-remaining");
// return false;
// }
// return true;
// }
// bool rateIsAcceptable(const Response &response)const{
//// todo validate rate type and number
// }
bool signatureIsNatValid(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 not signatureMatch;
}
bool hasException(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();
}
protected:
HttpHandler http{};
public:
CoreHandler(const Configuration & config) : secretKey{config.secretKey.data()}, rublonCore{config.apiServer.data()}, http{} {}
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
if(hasSignature(response) and signatureIsNatValid(response)) {
log(LogLevel::Error, "rublon core response is not signed properly");
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
}
return response;
}
// tl::expected< std::reference_wrapper< const Response >, Error > validateRate(const Response & response) const {
// if(hasSignature(response) and signatureIsNatValid(response)) {
// log(LogLevel::Error, "rublon core response is not signed properly");
// 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(hasException(resp)) {
const auto * exception = JSONPointer{"/result/exception", &alloc}.Get(resp);
log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception->GetString());
return handleCoreException(exception->GetString());
} else if(not hasSignature(response)) {
// additional check for mallformed responses (A invalid response, without any x-rublon-signature will stop at this check)
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
}
return resp;
}
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
// can happen only dyring check application step
if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value())
return tl::unexpected{Error{error.value()}};
// auth problems likely bad configuration in rublon admin console
if(auto error = RublonAuthenticationInterrupt::fromString(exceptionString); error.has_value())
return tl::unexpected{Error{error.value()}};
// verification error wrong passcode etc.
if(auto error = WerificationError::fromString(exceptionString); error.has_value())
return tl::unexpected{Error{error.value()}};
// other exceptions, just "throw"
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}};
}
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{rublonCore + path.data(), &memoryResource};
return http
.request(uri, request, response) //
.and_then(validateSignature)
.and_then(validateResponse)
.or_else(handleError);
}
bool createWSConnection(std::string_view tid) const {
if(not _ws) {
_ws = std::make_unique< WebSocket >();
}
return _ws->AttachToCore(rublonCore, tid);
}
auto listen() const {
return _ws->listen();
}
};
} // namespace rublon