diff --git a/PAM/ssh/src/CMakeLists.txt b/PAM/ssh/src/CMakeLists.txt index c1ffc96..98e5ef5 100644 --- a/PAM/ssh/src/CMakeLists.txt +++ b/PAM/ssh/src/CMakeLists.txt @@ -1,7 +1,9 @@ -set(CMAKE_SHARED_LINKER_FLAGS "-fpic -static-libstdc++ -fvisibility=hidden -ffunction-sections -fdata-sections -fwhole-program ") - add_library( rublon-ssh SHARED pam.cpp pam.hpp rublon.hpp curl.hpp span.hpp sign.hpp CoreHandler.hpp configuration.hpp ) +#set(CMAKE_SHARED_LINKER_FLAGS "-fpic -static-libstdc++ -fvisibility=hidden -ffunction-sections -fdata-sections -fwhole-program") + +target_link_options(rublon-ssh PUBLIC -fpic -shared) + target_link_libraries(rublon-ssh -lcurl -lssl -lcrypto) diff --git a/PAM/ssh/src/CoreHandler.hpp b/PAM/ssh/src/CoreHandler.hpp index fded6f5..4884510 100644 --- a/PAM/ssh/src/CoreHandler.hpp +++ b/PAM/ssh/src/CoreHandler.hpp @@ -27,6 +27,7 @@ template < typename Impl > class CoreHandlerInterface { public: tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const { + rublon::debugLog("[TMP] request",__PRETTY_FUNCTION__); return static_cast< const Impl * >(this)->request(path, body); } }; @@ -36,17 +37,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { std::string secretKey; std::string url; - std::pmr::string xRublonSignature(std::pmr::memory_resource * mr, std::string_view body) const { - return signData(body, secretKey.c_str()).data(); + std::pmr::string xRublonSignature(std::pmr::memory_resource & mr, std::string_view body) const { + return {signData(body, secretKey.c_str()).data(), &mr}; } void signRequest(std::pmr::monotonic_buffer_resource & mr, Request & request) const { - request.headers["X-Rublon-Signature"] = xRublonSignature(&mr, request.body); + rublon::debugLog("[TMP]",__PRETTY_FUNCTION__); + request.headers["X-Rublon-Signature"] = xRublonSignature(mr, request.body); } bool responseSigned(const Response & response) const { - auto xRubResp = response.headers.at("x-rublon-signature"); - auto sign = signData(response.body, secretKey); + rublon::debugLog("[TMP]",__PRETTY_FUNCTION__); + const auto & xRubResp = response.headers.at("x-rublon-signature"); + const auto & sign = signData(response.body, secretKey); return xRubResp == sign.data(); } @@ -59,10 +62,12 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { CoreHandler(const rublon::Configuration & config) : secretKey{config.parameters.secretKey}, url{config.parameters.apiServer} {} tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const { + rublon::debugLog("[TMP] request",__PRETTY_FUNCTION__); + std::byte _buffer[16 * 1024]; std::pmr::monotonic_buffer_resource mr{_buffer, sizeof(_buffer)}; - rublon::RapidJSONPMRAlloc alloc{&mr}; + rublon::StringBuffer jsonStr{&alloc}; rublon::Writer writer{jsonStr, &alloc}; @@ -73,31 +78,33 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { request.headers["Accept"] = "application/json"; request.body = jsonStr.GetString(); - + + rublon::debugLog("[TMP] request", "sign"); signRequest(mr, request); - std::pmr::string uri{url + path.data(), &mr}; - - auto response = http.request(uri, request); +// std::pmr::string uri{url + path.data(), &mr}; - if(not response.has_value()) { +// rublon::debugLog("[TMP] send", request.body.c_str()); +// auto response = http.request(uri, request); + +// if(not response.has_value()) { return tl::unexpected{CoreHandlerError::ConnectionError}; - } - if(not responseSigned(*response)) { - return tl::unexpected{CoreHandlerError::BadSigature}; - } +// } +// if(not responseSigned(*response)) { +// return tl::unexpected{CoreHandlerError::BadSigature}; +// } - rublon::Document resp{&alloc}; - resp.Parse(response->body.c_str()); +// rublon::Document resp{&alloc}; +// resp.Parse(response->body.c_str()); - if(resp.HasParseError() or not resp.HasMember("result")) { - return tl::unexpected{CoreHandlerError::BrokenData}; - } - if(resp["result"].HasMember("exception")) { - return tl::unexpected{CoreHandlerError{CoreHandlerError::CoreException, resp["result"]["exception"].GetString()}}; - } +// if(resp.HasParseError() or not resp.HasMember("result")) { +// return tl::unexpected{CoreHandlerError::BrokenData}; +// } +// if(resp["result"].HasMember("exception")) { +// return tl::unexpected{CoreHandlerError{CoreHandlerError::CoreException, resp["result"]["exception"].GetString()}}; +// } - return resp; +// return resp; } }; diff --git a/PAM/ssh/src/curl.hpp b/PAM/ssh/src/curl.hpp index 4405827..83fafae 100644 --- a/PAM/ssh/src/curl.hpp +++ b/PAM/ssh/src/curl.hpp @@ -11,12 +11,8 @@ #include -#include "span.hpp" #include "utils.hpp" -/// TODO rename file to rublon/Core.hpp -/// TODO Create rublon utils - namespace rublon { static size_t WriteMemoryCallback(void * contents, size_t size, size_t nmemb, void * userp) { size_t realsize = size * nmemb; @@ -42,6 +38,8 @@ class CURL { CURL() : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)} {} std::optional< Response > request(std::string_view uri, const Request &request) const { + rublon::debugLog("[TMP] request",__PRETTY_FUNCTION__); + std::string response_data; response_data.reserve(1000); @@ -63,7 +61,9 @@ class CURL { curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); - + + rublon::debugLog("[TMP] calling curl",__PRETTY_FUNCTION__); + auto res = curl_easy_perform(curl.get()); if(res != CURLE_OK) { // debugLog("No response from Rublon server (perform)", ""); diff --git a/PAM/ssh/src/json.hpp b/PAM/ssh/src/json.hpp index aba4959..5b14980 100644 --- a/PAM/ssh/src/json.hpp +++ b/PAM/ssh/src/json.hpp @@ -74,15 +74,4 @@ using Value = rapidjson::GenericValue< rapidjson::UTF8<>, RapidJSONPMRAlloc > using StringBuffer = rapidjson::GenericStringBuffer< rapidjson::UTF8<>, RapidJSONPMRAlloc >; using Writer = rapidjson::Writer< StringBuffer, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc >; -// using value = GenericValue< UTF8<>, RapidJSONPMRAlloc >; - -// RapidJSONPMRAlloc alloc{&mr}; -// GenericDocument< UTF8<>, RapidJSONPMRAlloc > d(&alloc); -// d.SetObject(); - -// GenericDocument< UTF8<>, RapidJSONPMRAlloc >::AllocatorType & allocator = d.GetAllocator(); - -// d.AddMember("systemToken", "DUPA", allocator); -// d.AddMember("username", "$USER", allocator); -// d.AddMember("userEmail", "$USER_EMAIL", allocator); } // namespace rublon diff --git a/PAM/ssh/src/pam.cpp b/PAM/ssh/src/pam.cpp index b0e6167..53c168d 100644 --- a/PAM/ssh/src/pam.cpp +++ b/PAM/ssh/src/pam.cpp @@ -1,36 +1,56 @@ #include -#include -#include #include #include +#include +#include #include -#include - -#include "curl.hpp" #include "rublon.hpp" +#include "utils.hpp" using namespace std; -PAM_EXTERN int pam_sm_setcred( pam_handle_t *pamh, int flags, int argc, const char **argv ) { +namespace { + +template < typename T > +using Expected = tl::expected< T, rublon::PamAction >; + +Expected< rublon::Method > chooseMethod() {} + +Expected< rublon::Confirm > confirm() {} + +} // namespace + +PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char ** argv) { + rublon::debugLog("[TMP]start", __PRETTY_FUNCTION__); + return PAM_SUCCESS; } -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char ** argv) { + rublon::debugLog("[TMP]start", __PRETTY_FUNCTION__); + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) { + rublon::debugLog("[TMP]start", __PRETTY_FUNCTION__); auto rublonConfig = rublon::ConfigurationFactory{}.systemConfig(); - + + rublon::debugLog("[TMP]create core handler", ""); rublon::CoreHandler CH{rublonConfig.value()}; - - rublon::Init{rublonConfig.value()}.fire(CH); - + + rublon::debugLog("[TMP]start", ""); + auto action = rublon::Init{pamh, rublonConfig.value()} + .fire(CH) // + .and_then([](const auto & method) -> Expected< int > { + rublon::debugLog("[TMP]start", "init lambda"); + return tl::unexpected{rublon::PamAction::accept}; + }); + +// .and_then([](const auto & value) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; }) // +// .and_then([](const auto & conf) { return tl::expected< rublon::Configuration, rublon::PamAction >{}; }); + return PAM_SUCCESS; } - -PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const char **argv ) { - return PAM_SUCCESS; -} - -int main(){ - pam_sm_acct_mgmt(nullptr, 1, 0, nullptr); -} diff --git a/PAM/ssh/src/pam.hpp b/PAM/ssh/src/pam.hpp index e261ef0..014f663 100644 --- a/PAM/ssh/src/pam.hpp +++ b/PAM/ssh/src/pam.hpp @@ -18,24 +18,39 @@ #include "utils.hpp" -class PamAction {}; - -class PamAccept {}; -class PamDecline {}; - -class PAMPrompt { +class LinuxPam { pam_handle_t * pamh; public: + LinuxPam(pam_handle_t * handler) : pamh{handler} {} + + rublon::NonOwningPtr< const char > ip() const { + const void * ip = NULL; + pam_get_item(pamh, PAM_RHOST, &ip); + if(ip == NULL) + ip = ""; + rublon::debugLog(__PRETTY_FUNCTION__, (const char*)ip); + return (const char*)ip; + } + + rublon::NonOwningPtr< const char > username() const { + const char * user = NULL; + pam_get_user(pamh, &user, nullptr); + if(user == NULL) + user = ""; + rublon::debugLog(__PRETTY_FUNCTION__, user); + return user; + } + template < typename... Ti > void print(const char * fmt, Ti... ti) const noexcept { - // pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward(ti...)); + pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti... >(ti...)); } template < typename Fun, typename... Ti > [[nodiscard]] auto scan(Fun && f, const char * fmt, Ti... ti) const noexcept { char * responseBuffer = nullptr; - pam_prompt(pamh, PAM_TEXT_INFO, &responseBuffer, fmt, std::forward< Ti... >(ti...)); + pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &responseBuffer, fmt, std::forward< Ti... >(ti...)); if(responseBuffer) { auto ret = f(responseBuffer); free(responseBuffer); @@ -44,20 +59,3 @@ class PAMPrompt { return std::optional< std::result_of_t< Fun(char *) > >(); } }; - -class LinuxPam { - pam_handle_t * pamh; - - public: - rublon::NonOwningPtr< const char > ip() const { - const char * ip = NULL; - pam_get_item(pamh, PAM_RHOST, ( const void ** ) &ip); - if(ip == NULL) - ip = ""; - return ip; - } - - rublon::NonOwningPtr< const char > username() const { - // pam_get_user - } -}; diff --git a/PAM/ssh/src/rublon.hpp b/PAM/ssh/src/rublon.hpp index 1eb1da4..8795356 100644 --- a/PAM/ssh/src/rublon.hpp +++ b/PAM/ssh/src/rublon.hpp @@ -26,85 +26,147 @@ enum class PamAction { accept, decline }; template < typename Impl > class AuthenticationStep { public: - template < typename Hander_t > - auto fire(const CoreHandlerInterface< Hander_t > & coreHandler) { + template < typename Handler_t > + auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) { // log step // debugLog("Starting %s step", static_cast(this)->stepName ); return static_cast< Impl * >(this)->handle(coreHandler); } }; -template < typename PamInfo_t = PAMInfo > -class Method : public AuthenticationStep< Method< PamInfo_t > > { +class Confirm : public AuthenticationStep< Confirm > { + public: + Confirm(const Configuration & /*config*/) {} +}; + +class MethodOTP {}; + +class MethodSMS {}; + + +class Method : public AuthenticationStep< Method > { std::string tid; public: Method() {} + + template < typename Handler_t > + tl::expected< Confirm, PamAction > fire(const CoreHandlerInterface< Handler_t > & coreHandler) {} + + private: + std::variant< MethodOTP, MethodSMS > _impl; }; -template < typename PamInfo_t = PAMInfo > -class Init : public AuthenticationStep< Init< PamInfo_t > > { - const char * apiPath = "/api/transaction/init"; - const std::string & systemToken; +template < typename Pam_t = LinuxPam > +class MethodFactory { + const Pam_t & pam; - PamInfo_t pamInfo; + public: + MethodFactory(const Pam_t & pam) : pam{pam} {} + + template < typename Array_t > + tl::expected< Method, PamAction > create(const Array_t & methods) const { + rublon::debugLog("[TMP] ",__PRETTY_FUNCTION__); + std::pmr::map< int, std::pmr::string > methods_id; + pam.print("%s", ""); + int i; + for(const auto & method : methods) { + if(method == "email") { + pam.print("%d: Email Link", i + 1); + methods_id[++i] = "email"; + } + if(method == "qrcode") { + pam.print("%d: QR Code", i + 1); + methods_id[++i] = "qrcode"; + } + if(method == "totp") { + pam.print("%d: Mobile TOTP", i + 1); + methods_id[++i] = "totp"; + } + if(method == "push") { + pam.print("%d: Mobile Push", i + 1); + methods_id[++i] = "push"; + } + if(method == "sms") { + pam.print("%d: SMS code", i + 1); + methods_id[++i] = "sms"; + } + } + + auto methodid = pam.scan([](char * userinput) { return std::stoi(userinput); }, "\nSelect method [1-%d]: ", methods.Size()); + + pam.print("you selected: %s", methods_id.count(methodid.value_or(0)) ? methods_id.at(methodid.value_or(0)).c_str() : "unknown option"); + + return tl::unexpected{PamAction::accept}; + } +}; + +template < template < typename > class MethodFactory_t = MethodFactory, typename PamInfo_t = LinuxPam > +class Init : public AuthenticationStep< Init< MethodFactory_t, PamInfo_t > > { + const char * apiPath = "/api/transaction/init"; + const std::string & _systemToken; + + protected: + PamInfo_t _pamInfo; + MethodFactory_t< PamInfo_t > _methodFactory; public: const char * stepName = "Initialization"; - Init(const rublon::Configuration & config) : systemToken{config.parameters.systemToken} {} + Init(pam_handle_t * pamHandler, const rublon::Configuration & config) + : _systemToken{config.parameters.systemToken}, _pamInfo{pamHandler}, _methodFactory{_pamInfo} { + rublon::debugLog("[TMP] Init ctor",__PRETTY_FUNCTION__); + } /// TODO add core handler interface template < typename Hander_t > - std::variant< Method< PamInfo_t >, PamAction > handle(const CoreHandlerInterface< Hander_t > & handler) const { + tl::expected< Method, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { + rublon::debugLog("[TMP] init handle",__PRETTY_FUNCTION__); char _buffer[1024]; std::pmr::monotonic_buffer_resource mr{_buffer, 1024}; RapidJSONPMRAlloc alloc{&mr}; Document body{rapidjson::kObjectType, &alloc}; - body.AddMember("systemToken", Value{systemToken.c_str(), alloc}, alloc); - body.AddMember("username", Value{pamInfo.username().get(), alloc}, alloc); - body.AddMember("userEmail", "bwi@rublon.com", alloc); + body.AddMember("systemToken", Value{_systemToken.c_str(), alloc}, alloc); + body.AddMember("username", Value{_pamInfo.username().get(), alloc}, alloc); + body.AddMember("userEmail", "bwi@rublon.com", alloc); /// TODO proper username Value params{rapidjson::kObjectType}; - params.AddMember("userIP", Value{pamInfo.ip().get(), alloc}, alloc); + params.AddMember("userIP", Value{_pamInfo.ip().get(), alloc}, alloc); params.AddMember("appVer", "v.1.6", alloc); /// TODO add version to cmake params.AddMember("os", "Ubuntu 23.04", alloc); /// TODO add version to cmake body.AddMember("params", std::move(params), alloc); + rublon::debugLog("[TMP] calling coreHandler",__PRETTY_FUNCTION__); + auto httpResponse = coreHandler.request(apiPath, body); - auto response = handler.request(apiPath, body); +// if(httpResponse.has_value()) { +// rublon::debugLog("[TMP] has response, processing",__PRETTY_FUNCTION__); +// const auto & rublonResponse = httpResponse.value()["response"]; - if(response.has_value()) { - std::cout << response.value()["response"]["tid"].GetString(); +// std::string tid = rublonResponse["tid"].GetString(); - return Method< PamInfo_t >{ +// return _methodFactory.create(rublonResponse["methods"].GetArray()); +// } else { +// // mostly connectio errors +// switch(httpResponse.error().errorClass) { +// case CoreHandlerError::ErrorClass::BadSigature: +// return tl::unexpected{PamAction::decline}; +// case CoreHandlerError::ErrorClass::CoreException: /// TODO exception handling +// return tl::unexpected{PamAction::decline}; /// TODO accept? +// case CoreHandlerError::ErrorClass::ConnectionError: +// return tl::unexpected{PamAction::decline}; /// TODO decline? +// case CoreHandlerError::ErrorClass::BrokenData: +// return tl::unexpected{PamAction::decline}; +// } +// } - }; - - } else { - // mostly connectio errors - switch(response.error().errorClass) { - case CoreHandlerError::ErrorClass::BadSigature: - return PamAction::decline; /// TODO accept? - case CoreHandlerError::ErrorClass::CoreException: - return PamAction::decline; /// TODO accept? - case CoreHandlerError::ErrorClass::ConnectionError: - return PamAction::decline; /// TODO accept? - case CoreHandlerError::ErrorClass::BrokenData: - return PamAction::decline; - } - } - - return {PamAction::decline}; + return tl::unexpected{PamAction::decline}; } }; -class ConfirmCode : public AuthenticationStep< ConfirmCode > { - public: - ConfirmCode(const Configuration & /*config*/) {} -}; + class VerifySSH : public AuthenticationStep< VerifySSH > { public: diff --git a/PAM/ssh/src/sign.hpp b/PAM/ssh/src/sign.hpp index 89459ee..76e4701 100644 --- a/PAM/ssh/src/sign.hpp +++ b/PAM/ssh/src/sign.hpp @@ -1,21 +1,20 @@ #pragma once +#include #include #include #include -#include -#include #include -#include - namespace rublon { inline std::array< char, 64 > signData(std::string_view data, std::string_view secretKey) { std::array< char, 64 > xRublon; unsigned char md[EVP_MAX_MD_SIZE] = {0}; unsigned int md_len; + HMAC(EVP_sha256(), secretKey.data(), secretKey.size(), ( unsigned const char * ) data.data(), data.size(), md, &md_len); + int i; for(i = 0; i < 32; i++) sprintf(&xRublon[i * 2], "%02x", ( unsigned int ) md[i]); diff --git a/PAM/ssh/src/utils.hpp b/PAM/ssh/src/utils.hpp index 53c70c7..be76817 100644 --- a/PAM/ssh/src/utils.hpp +++ b/PAM/ssh/src/utils.hpp @@ -9,9 +9,26 @@ #include #include +#include + +#include +#include +#include namespace rublon { +inline void debugLog(const char * message1, const char * message2) { + auto file_name = "/tmp/rublon.log"; + auto fp = fopen(file_name, "a"); + fprintf(fp, "[%s] %s %s\n", "cmake version", message1, message2); + fflush(fp); + fclose(fp); + +// openlog("pam_rublon", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); +// syslog(LOG_NOTICE, "[%s] %s %s", "cmake version", message1, message2); +// closelog(); +} + template < typename T > class NonOwningPtr { T * object; @@ -33,6 +50,12 @@ class NonOwningPtr { constexpr operator T *() noexcept { return get(); } + constexpr T * operator->() { + return get(); + } + constexpr const T * operator->() const { + return get(); + } }; namespace details { diff --git a/PAM/ssh/tests/init_test.cpp b/PAM/ssh/tests/init_test.cpp index ee934b7..651d95e 100644 --- a/PAM/ssh/tests/init_test.cpp +++ b/PAM/ssh/tests/init_test.cpp @@ -9,50 +9,107 @@ using namespace rublon; using namespace testing; namespace { - Configuration conf; +Configuration conf; } - + class CoreHandlerMock : public CoreHandlerInterface< CoreHandlerMock > { public: CoreHandlerMock() {} MOCK_METHOD(( tl::expected< Document, CoreHandlerError > ), request, ( std::string_view, const Document & ), (const)); - + CoreHandlerMock & statusPending() { gen.status = "pending"; return *this; } - + CoreHandlerMock & brokenBody() { gen.generateBrokenData = true; return *this; } - + operator tl::expected< Document, CoreHandlerError >() { auto body = gen.generateBody(); - + rublon::Document doc; doc.Parse(body.c_str()); - + return doc; } - + CoreResponseGenerator gen; }; +class PamInfoMock { + public: + PamInfoMock(pam_handle_t *) {} + MOCK_METHOD(rublon::NonOwningPtr< const char >, ip, (), (const)); + MOCK_METHOD(rublon::NonOwningPtr< const char >, username, (), (const)); +}; + +template < typename Pam > +class MethodFactoryMock { + public: + MethodFactoryMock(const Pam &) {} + template < typename Array_t > + tl::expected< Method, PamAction > create(const Array_t & methods) const {} +}; + +class InitTestable : public Init< MethodFactoryMock, PamInfoMock > { + public: + InitTestable(const rublon::Configuration & conf) : Init{nullptr, conf} {} + PamInfoMock & pam() { + return _pamInfo; + } +}; + class RublonHttpInitTest : public testing::Test { public: + void expectDefaultPamInfo() { + EXPECT_CALL(pam, ip()).WillOnce(Return("192.168.0.1")); + EXPECT_CALL(pam, username()).WillOnce(Return("bwi")); + } + + RublonHttpInitTest() : coreHandler{}, sut{conf}, pam{sut.pam()} {} CoreHandlerMock coreHandler; - Init<> sut{conf}; + InitTestable sut{conf}; + PamInfoMock & pam; }; using CoreReturn = tl::expected< Document, CoreHandlerError >; TEST_F(RublonHttpInitTest, initializationSendsRequestOnGoodPath) { - EXPECT_CALL(coreHandler, request("/api/transaction/init", _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}})); + expectDefaultPamInfo(); + EXPECT_CALL(coreHandler, request("/api/transaction/init", _)) + .WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}})); sut.handle(coreHandler); } -TEST_F(RublonHttpInitTest, returnMethods){ - +MATCHER_P(HoldsPamAction, action, "") { + return not arg.has_value() && arg.error() == action; } + +TEST_F(RublonHttpInitTest, rublon_Accept_pamLoginWhenThereIsNoConnection) { + expectDefaultPamInfo(); + EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::ConnectionError}})); + EXPECT_THAT(sut.handle(coreHandler), HoldsPamAction(PamAction::decline)); +} + +TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerHasBadSignature) { + expectDefaultPamInfo(); + EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}})); + EXPECT_THAT(sut.handle(coreHandler), HoldsPamAction(PamAction::decline)); +} + +TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerReturnsBrokenData) { + expectDefaultPamInfo(); + EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}})); + EXPECT_THAT(sut.handle(coreHandler), HoldsPamAction(PamAction::decline)); +} + +TEST_F(RublonHttpInitTest, rublon_Decline_pamLoginWhenServerReturnsCoreException) { + expectDefaultPamInfo(); + EXPECT_CALL(coreHandler, request(_, _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::CoreException}})); + EXPECT_THAT(sut.handle(coreHandler), HoldsPamAction(PamAction::decline)); +} +