diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt index 55bce18..f13a2a0 100644 --- a/PAM/ssh/CMakeLists.txt +++ b/PAM/ssh/CMakeLists.txt @@ -5,23 +5,26 @@ add_library(rublon-ssh INTERFACE ) -add_library(rublon-ssh_ide INTERFACE - ./include/rublon/authentication_step_interface.hpp - ./include/rublon/configuration.hpp - ./include/rublon/core_handler.hpp - ./include/rublon/core_handler_interface.hpp - ./include/rublon/curl.hpp - ./include/rublon/init.hpp - ./include/rublon/json.hpp - ./include/rublon/method/method_factory.hpp - ./include/rublon/method/OTP.hpp - ./include/rublon/method/SMS.hpp - ./include/rublon/pam_action.hpp - ./include/rublon/pam.hpp - ./include/rublon/rublon.hpp - ./include/rublon/sign.hpp - ./include/rublon/span.hpp - ./include/rublon/utils.hpp) +if(${CMAKE_VERSION} VERSION_GREATER "3.19.0") + add_library(rublon-ssh_ide INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/authentication_step_interface.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/configuration.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/init.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_factory.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/OTP.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/SMS.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam_action.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/pam.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/rublon.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/sign.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/span.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/utils.hpp + ) +endif() target_include_directories(rublon-ssh INTERFACE diff --git a/PAM/ssh/include/rublon/authentication_step_interface.hpp b/PAM/ssh/include/rublon/authentication_step_interface.hpp index 717efa2..fbc7d97 100644 --- a/PAM/ssh/include/rublon/authentication_step_interface.hpp +++ b/PAM/ssh/include/rublon/authentication_step_interface.hpp @@ -9,10 +9,10 @@ template < typename Impl > class AuthenticationStep { public: template < typename Handler_t > - auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) { + auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) const { // log step - log(Info, "Starting %s step", static_cast< Impl * >(this)->name); - return static_cast< Impl * >(this)->handle(coreHandler); + log(Info, "Starting %s step", static_cast< const Impl * >(this)->name); + return static_cast< const Impl * >(this)->handle(coreHandler); } }; diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp index 1adf529..aa684ec 100644 --- a/PAM/ssh/include/rublon/core_handler.hpp +++ b/PAM/ssh/include/rublon/core_handler.hpp @@ -42,22 +42,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { HttpHandler http{}; public: - void debugLog(const char *, const char *) {} - 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::log(LogLevel::Info, "Sending request to %s", path.data()); - + log(Debug, "CoreHandler::Request prepare"); 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}; - + body.Accept(writer); - + Request request; request.headers["Content-Type"] = "application/json"; request.headers["Accept"] = "application/json"; @@ -65,7 +62,6 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { request.body = jsonStr.GetString(); signRequest(mr, request); - std::pmr::string uri{url + path.data(), &mr}; auto response = http.request(uri, request); diff --git a/PAM/ssh/include/rublon/core_handler_interface.hpp b/PAM/ssh/include/rublon/core_handler_interface.hpp index 9c516fa..c7f9f03 100644 --- a/PAM/ssh/include/rublon/core_handler_interface.hpp +++ b/PAM/ssh/include/rublon/core_handler_interface.hpp @@ -21,7 +21,7 @@ template < typename Impl > class CoreHandlerInterface { public: tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const { - rublon::log(LogLevel::Debug, "%s", __PRETTY_FUNCTION__); + rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request"); return static_cast< const Impl * >(this)->request(path, body); } }; diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp index 1d0244d..61d0113 100644 --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -46,6 +46,7 @@ class CURL { /// 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); std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) { + log(Debug, "header: %s: %s", header.first.c_str(), header.second.c_str()); curl_headers.reset(curl_slist_append(curl_headers.release(), (header.first + ": " + header.second).c_str())); }); @@ -59,15 +60,15 @@ class CURL { curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); - rublon::log(LogLevel::Info, "Request send uri:{%s} body:{%s}", uri.data(), request.body.c_str()); + log(Info, "Request send uri:%s body:\n%s\n", uri.data(), request.body.c_str()); auto res = curl_easy_perform(curl.get()); if(res != CURLE_OK) { - log(LogLevel::Error, "No response from Rublon server err:{%s}", curl_easy_strerror(res)); + log(Error, "No response from Rublon server err:{%s}", curl_easy_strerror(res)); return std::nullopt; } - - log(LogLevel::Debug, "Response data :{{ %s }}", response_data.c_str()); - + + log(Debug, "Response:\n%s\n", response_data.c_str()); + long size; curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size); diff --git a/PAM/ssh/include/rublon/init.hpp b/PAM/ssh/include/rublon/init.hpp index 5e4ae9f..adaf656 100644 --- a/PAM/ssh/include/rublon/init.hpp +++ b/PAM/ssh/include/rublon/init.hpp @@ -9,51 +9,55 @@ #include namespace rublon{ +class Verify{}; +} + +namespace rublon { 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 * name = "Initialization"; - + Init(pam_handle_t * pamHandler, const rublon::Configuration & config) - : _systemToken{config.parameters.systemToken}, _pamInfo{pamHandler}, _methodFactory{_pamInfo} {} - - /// TODO add core handler interface + : _systemToken{config.parameters.systemToken}, _pamInfo{pamHandler}, _methodFactory{_pamInfo, config.parameters.systemToken} {} + + /// TODO add core handler interface template < typename Hander_t > - tl::expected< Method, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { + tl::expected< Method, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { 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); /// TODO proper username - + Value params{rapidjson::kObjectType}; 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); - - auto httpResponse = coreHandler.request(apiPath, body); - - if(httpResponse.has_value()) { + + auto coreResponse = coreHandler.request(apiPath, body); + + if(coreResponse.has_value()) { log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__); - const auto & rublonResponse = httpResponse.value()["result"]; - std::string tid = rublonResponse["tid"].GetString(); + const auto & rublonResponse = coreResponse.value()["result"]; + std::string tid = rublonResponse["tid"].GetString(); return _methodFactory.create(rublonResponse["methods"].GetArray(), std::move(tid)); } else { // mostly connectio errors - switch(httpResponse.error().errorClass) { + switch(coreResponse.error().errorClass) { case CoreHandlerError::ErrorClass::BadSigature: log(LogLevel::Error, "ErrorClass::BadSigature"); return tl::unexpected{PamAction::decline}; @@ -68,8 +72,8 @@ class Init : public AuthenticationStep< Init< MethodFactory_t, PamInfo_t > > { return tl::unexpected{PamAction::decline}; } } - + return tl::unexpected{PamAction::decline}; } }; -} +} // namespace rublon diff --git a/PAM/ssh/include/rublon/method/OTP.hpp b/PAM/ssh/include/rublon/method/OTP.hpp index f72bcfc..aa76811 100644 --- a/PAM/ssh/include/rublon/method/OTP.hpp +++ b/PAM/ssh/include/rublon/method/OTP.hpp @@ -3,12 +3,71 @@ #include #include +#include +#include + +namespace rublon { +class Confirmation {}; +}; // namespace rublon namespace rublon::method { -class OTP : public rublon::AuthenticationStep< OTP > { +template < typename PamInfo_t = LinuxPam > +class OTP : public rublon::AuthenticationStep< OTP< PamInfo_t > > { public: - const char * name = "Select 'One Time Password' method"; + const char * uri = "/api/transaction/confirmCode"; + const char * name = "One Time Password"; + + std::string _systemToken; + std::string _tid; + const PamInfo_t & _pamInfo; + + OTP(std::string systemToken, std::string tid, const PamInfo_t & pam) : _systemToken{systemToken}, _tid{tid}, _pamInfo{pam} {} + + template < typename Hander_t > + tl::expected< Confirmation, PamAction > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { + log(Debug, "OTP fired"); + + const auto passcode = + _pamInfo.scan([](const char * userInput) { return std::string{userInput}; }, "Mobile TOTP from Rublon Authenticator:"); + + 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("tid", Value{_tid.c_str(), alloc}, alloc); + body.AddMember("vericode", Value{passcode.value().c_str(), alloc}, alloc); /// TODO proper username + + auto coreResponse = coreHandler.request(uri, body); + + if(coreResponse.has_value()) { + log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__); + const auto & rublonResponse = coreResponse.value()["result"]; + std::string tid = rublonResponse["tid"].GetString(); + return tl::unexpected{PamAction::accept}; + } else { + // mostly connectio errors + switch(coreResponse.error().errorClass) { + case CoreHandlerError::ErrorClass::BadSigature: + log(LogLevel::Error, "ErrorClass::BadSigature"); + return tl::unexpected{PamAction::decline}; + case CoreHandlerError::ErrorClass::CoreException: /// TODO exception handling + log(LogLevel::Error, "ErrorClass::CoreException"); + return tl::unexpected{PamAction::decline}; /// TODO accept? + case CoreHandlerError::ErrorClass::ConnectionError: + log(LogLevel::Error, "ErrorClass::ConnectionError"); + return tl::unexpected{PamAction::decline}; /// TODO decline? + case CoreHandlerError::ErrorClass::BrokenData: + log(LogLevel::Error, "ErrorClass::BrokenData"); + return tl::unexpected{PamAction::decline}; + } + } + + return tl::unexpected{PamAction::accept}; + } }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/SMS.hpp b/PAM/ssh/include/rublon/method/SMS.hpp index 2583dcc..cfb7dda 100644 --- a/PAM/ssh/include/rublon/method/SMS.hpp +++ b/PAM/ssh/include/rublon/method/SMS.hpp @@ -1,5 +1,8 @@ #pragma once namespace rublon::method { -class SMS {}; -} // namespace rublon +class SMS { + public: + const char * name = "SMS"; +}; +} // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/method_factory.hpp b/PAM/ssh/include/rublon/method/method_factory.hpp index 7c127f0..a2946c2 100644 --- a/PAM/ssh/include/rublon/method/method_factory.hpp +++ b/PAM/ssh/include/rublon/method/method_factory.hpp @@ -12,34 +12,83 @@ #include namespace rublon { - +template < typename Pam_t = LinuxPam > class Method { - std::string tid; + std::string _tid; + std::string _systemToken; public: - Method() {} + template < typename Method_t > + Method(Method_t && metho, std::string systemToken, std::string tid) : _impl{metho}, _systemToken{systemToken}, _tid{tid}{} template < typename Handler_t > - tl::expected< int, PamAction > fire(const CoreHandlerInterface< Handler_t > & coreHandler) { - return std::visit([&](const auto & method) { return method.fire(coreHandler); }, _impl); + tl::expected< Confirmation, PamAction > fire(const CoreHandlerInterface< Handler_t > & coreHandler) const { + log(Debug, "method select"); + + + 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("tid", Value{_tid.c_str(), alloc}, alloc); + body.AddMember("method", Value{"totp", alloc}, alloc); + body.AddMember("GDPRAccepted", Value{"true", alloc}, alloc); + body.AddMember("tosAccepted", Value{"true", alloc}, alloc); + + auto coreResponse = coreHandler.request("/api/transaction/methodSSH", body); + + if(coreResponse.has_value()) { + log(LogLevel::Info, "[TMP] has response, processing", __PRETTY_FUNCTION__); + const auto & rublonResponse = coreResponse.value()["result"]; + std::string tid = rublonResponse["tid"].GetString(); + return tl::unexpected{PamAction::accept}; + } else { + // mostly connectio errors + switch(coreResponse.error().errorClass) { + case CoreHandlerError::ErrorClass::BadSigature: + log(LogLevel::Error, "ErrorClass::BadSigature"); + return tl::unexpected{PamAction::decline}; + case CoreHandlerError::ErrorClass::CoreException: /// TODO exception handling + log(LogLevel::Error, "ErrorClass::CoreException"); + return tl::unexpected{PamAction::decline}; /// TODO accept? + case CoreHandlerError::ErrorClass::ConnectionError: + log(LogLevel::Error, "ErrorClass::ConnectionError"); + return tl::unexpected{PamAction::decline}; /// TODO decline? + case CoreHandlerError::ErrorClass::BrokenData: + log(LogLevel::Error, "ErrorClass::BrokenData"); + return tl::unexpected{PamAction::decline}; + } + } + + + return std::visit( + [&](const auto & method) { + rublon::log(LogLevel::Info, "Using '%s' method", method.name); + return method.fire(coreHandler); + }, + _impl); } private: - std::variant< method::OTP, method::SMS > _impl; + std::variant< method::OTP > _impl; }; template < typename Pam_t = LinuxPam > class MethodFactory { + std::string systemToken; const Pam_t & pam; public: - MethodFactory(const Pam_t & pam) : pam{pam} {} + MethodFactory(const Pam_t & pam, std::string systemToken) : pam{pam}, systemToken{systemToken} {} template < typename Array_t > - tl::expected< Method, PamAction > create(const Array_t & methods, const std::string & tid) const { + tl::expected< Method, PamAction > create(const Array_t & methods, const std::string & tid) const { std::pmr::map< int, std::pmr::string > methods_id; pam.print("%s", ""); - int i; + int i{}; for(const auto & method : methods) { rublon::log(LogLevel::Debug, "method %s found at pos %d", method.GetString(), i); if(method == "email") { @@ -74,7 +123,11 @@ class MethodFactory { pam.print( "you selected: %s", methods_id.count(methodid.value_or(0)) ? methods_id.at(methodid.value_or(0)).c_str() : "unknown option"); - + + if(methods_id.at(methodid.value_or(0)) == "totp"){ + return Method{method::OTP{systemToken,std::move(tid), pam}, systemToken, tid}; + } + return tl::unexpected{PamAction::accept}; } }; diff --git a/PAM/ssh/include/rublon/rublon.hpp b/PAM/ssh/include/rublon/rublon.hpp index 54829b3..7d7ca0e 100644 --- a/PAM/ssh/include/rublon/rublon.hpp +++ b/PAM/ssh/include/rublon/rublon.hpp @@ -27,8 +27,6 @@ class Confirm : public AuthenticationStep< Confirm > { Confirm(const Configuration & /*config*/) {} }; - - class VerifySSH : public AuthenticationStep< VerifySSH > { public: VerifySSH(const Configuration & /*config*/) {} diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp index 9b2e660..cce7a54 100644 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace rublon { enum LogLevel { Debug, Info, Warning, Error }; @@ -40,19 +41,20 @@ namespace details{ if(fp) { fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[(int)level], line); + sync(); } } } inline void log(LogLevel level, const char * line) noexcept { - if(level > g_level) + if(level < g_level) return; details::doLog(level, line); } template < typename... Ti > void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept { - if(level > g_level) + if(level < g_level) return; std::array< char, 1000 > line; @@ -98,7 +100,7 @@ namespace details { std::transform(value.cbegin(), value.cend(), buf, asciitolower); return strcmp(buf, "true") == 0; - }; + } static inline std::string_view ltrim(std::string_view s) { while(s.length() && std::isspace(*s.begin())) diff --git a/PAM/ssh/lib/CMakeLists.txt b/PAM/ssh/lib/CMakeLists.txt index 88d8917..ae7f113 100644 --- a/PAM/ssh/lib/CMakeLists.txt +++ b/PAM/ssh/lib/CMakeLists.txt @@ -12,14 +12,15 @@ target_compile_options(rublon-ssh-pam -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables + -flto ) target_link_options(rublon-ssh-pam PUBLIC -fpic -s -# -static-libstdc++ -Wl,--gc-sections + -flto ) target_link_libraries(rublon-ssh-pam diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp index a00ab85..2e47670 100644 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -11,7 +11,7 @@ #include -#define DLL_PUBLIC __attribute__ ((visibility ("default"))) +#define DLL_PUBLIC __attribute__((visibility("default"))) using namespace std; @@ -20,32 +20,36 @@ namespace { template < typename T > using Expected = tl::expected< T, rublon::PamAction >; -Expected< rublon::Method > chooseMethod() {} - -Expected< rublon::Confirm > confirm() {} - } // namespace -DLL_PUBLIC int pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char ** argv) { +DLL_PUBLIC int pam_sm_setcred([[maybe_unused]] pam_handle_t * pamh, + [[maybe_unused]] int flags, + [[maybe_unused]] int argc, + [[maybe_unused]] const char ** argv) { return PAM_SUCCESS; } -DLL_PUBLIC int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char ** argv) { +DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh, + [[maybe_unused]] int flags, + [[maybe_unused]] int argc, + [[maybe_unused]] const char ** argv) { return PAM_SUCCESS; } -DLL_PUBLIC int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) { +DLL_PUBLIC int +pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { + rublon::log(rublon::LogLevel::Debug, "pam start"); auto rublonConfig = rublon::ConfigurationFactory{}.systemConfig(); rublon::CoreHandler CH{rublonConfig.value()}; + + rublon::log(rublon::LogLevel::Debug, "Init"); + auto method = rublon::Init{pamh, rublonConfig.value()}.fire(CH); + rublon::log(rublon::LogLevel::Debug, "Method"); + + method.value().fire(CH); - auto action = rublon::Init{pamh, rublonConfig.value()} - .fire(CH) // - .and_then([](const auto & method) -> Expected< int > { - 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 >{}; }); + // .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; } diff --git a/PAM/ssh/tests/init_test.cpp b/PAM/ssh/tests/init_test.cpp index 008a174..3bacb0a 100644 --- a/PAM/ssh/tests/init_test.cpp +++ b/PAM/ssh/tests/init_test.cpp @@ -64,9 +64,10 @@ class PamInfoMock { template < typename Pam > class MethodFactoryMock { public: - using MethodFactoryCreate_t = tl::expected< Method, PamAction >; - - MethodFactoryMock(const Pam &) {} + using MethodFactoryCreate_t = tl::expected< Method, PamAction >; + + template + MethodFactoryMock(Args && ... ) {} MOCK_METHOD(MethodFactoryCreate_t, create_mocked, (std::vector< std::string > methods, std::string tid), (const));