rublon-ssh/PAM/ssh/include/rublon/method/passcode_based_auth.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

163 lines
6.2 KiB
C++
Executable File

#pragma once
#include "rublon/utils.hpp"
#include <tl/expected.hpp>
#include <rublon/authentication_step_interface.hpp>
#include <rublon/pam.hpp>
#include <rublon/pam_action.hpp>
#include <rublon/websockets.hpp>
namespace rublon::method {
class PasscodeBasedAuth : public AuthenticationStep {
protected:
const char * uri;
const char * confirmField;
static constexpr const char * confirmCodeEndpoint = "/api/transaction/confirmCode";
static constexpr const char * confirmSecuritySSHEndpoint = "/api/transaction/confirmSecurityKeySSH";
static constexpr const char * fieldVericode = "vericode";
static constexpr const char * fieldOtp = "otp";
const char * userMessage{nullptr};
const uint_fast8_t length;
const bool onlyDigits;
int _prompts;
constexpr static bool isdigit(char ch) {
return std::isdigit(static_cast< unsigned char >(ch));
}
bool digitsOnly(std::string_view userinput) const {
return std::all_of(userinput.cbegin(), userinput.cend(), isdigit);
}
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 >
tl::expected< std::reference_wrapper< Document >, Error > readPasscode(Document & body, const PamInfo_t & pam) const {
auto & alloc = body.GetAllocator();
auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage);
if(hasValidLength(vericode) and hasValidCharacters(vericode)) {
Value confirmFieldValue(confirmField, alloc);
body.AddMember(confirmFieldValue, Value{vericode.c_str(), alloc}, alloc);
if(token.size()) {
this->addAccessToken(body, token);
}
return body;
}
return tl::unexpected{Error{WerificationError{WerificationError::BadInput}}};
}
template < typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & /*coreResponse*/, const PamInfo_t & pam) const {
pam.print("The passcode has been validated");
return AuthenticationStatus{AuthenticationStatus::Action::Confirmed};
}
tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation(
std::shared_ptr< WebSocketSingleShotEventListener > eventListener) const {
log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth");
return eventListener->waitForEvent();
}
template < typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const PamInfo_t & pam, int promptLeft) const {
if(promptLeft && error.is< WerificationError >()) {
switch(error.get< WerificationError >().errorClass) {
case WerificationError::ErrorClass::PasscodeException:
pam.print(R"(Incorrect passcode. Try again)");
break;
case WerificationError::ErrorClass::BadInput:
pam.print("The passcode has an incorrect number of digits or contains invalid characters. Try again");
break;
}
}
return tl::unexpected{error};
}
public:
const char * name;
std::string token;
enum class Endpoint { ConfirmCode, SecurityKeySSH };
PasscodeBasedAuth(std::string systemToken,
std::string tid,
std::string token,
const char * name,
const char * userMessage,
uint_fast8_t length,
bool numbersOnly,
Endpoint endpoint,
int prompts)
: AuthenticationStep(std::move(systemToken), std::move(tid)),
uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint},
confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp},
userMessage{userMessage},
length{length},
onlyDigits{numbersOnly},
_prompts{prompts},
name{name},
token{std::move(token)} {}
template < typename Hander_t, typename PamInfo_t = LinuxPam >
tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const {
RapidJSONPMRStackAlloc< 2048 > alloc{};
Document body{rapidjson::kObjectType, &alloc};
std::shared_ptr< WebSocketSingleShotEventListener > eventListener = coreHandler.listen();
int prompts = _prompts;
const auto requestAuthorization = [&](const auto & body) { return coreHandler.request(alloc, uri, body); };
const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); };
const auto waitForCoreToConfirm = [&](const auto &) { return waitForCoreConfirmation(eventListener); };
const auto handleError = [&](const auto error) { return errorHandler(error, pam, prompts); };
this->addSystemToken(body);
this->addTid(body);
return [&]() {
while(true) {
prompts--;
auto access = readPasscode(body, pam) //
.and_then(requestAuthorization)
.and_then(checkCodeValidity)
.and_then(waitForCoreToConfirm)
.or_else(handleError);
if(not access && prompts) {
continue;
} else {
return access;
}
};
}();
}
};
} // namespace rublon::method