From 45a50483be664c27eccccb7ebb5904c477175653 Mon Sep 17 00:00:00 2001 From: stephb9959 Date: Tue, 9 Nov 2021 11:33:20 -0800 Subject: [PATCH] Implementing several adjustments for security reasons. --- CMakeLists.txt | 5 +- build | 2 +- src/ActionLinkManager.cpp | 64 +++++++ src/ActionLinkManager.h | 35 ++++ src/AuthService.cpp | 93 +++++++--- src/AuthService.h | 16 +- src/Daemon.cpp | 3 + src/MFAServer.cpp | 2 - src/MFAServer.h | 11 +- src/RESTAPI/RESTAPI_action_links.cpp | 47 +++-- src/RESTAPI/RESTAPI_action_links.h | 9 +- src/RESTAPI/RESTAPI_oauth2Handler.cpp | 16 +- src/RESTAPI/RESTAPI_oauth2Handler.h | 2 +- src/RESTAPI/RESTAPI_user_handler.cpp | 9 +- src/RESTAPI/RESTAPI_users_handler.cpp | 8 +- src/RESTObjects/RESTAPI_SecurityObjects.cpp | 59 ++++++- src/RESTObjects/RESTAPI_SecurityObjects.h | 38 ++-- src/SMSSender.cpp | 1 - src/SMSSender.h | 7 +- src/SMTPMailerService.cpp | 2 - src/SMTPMailerService.h | 7 +- src/StorageService.cpp | 2 - src/StorageService.h | 44 +---- src/framework/MicroService.h | 165 +++++++++++++---- src/storage/storage_actionLinks.cpp | 186 ++++++++++++++++++++ src/storage/storage_actionLinks.h | 79 +++++++++ src/storage/storage_avatar.cpp | 13 +- src/storage/storage_avatar.h | 27 +++ src/storage/storage_tables.cpp | 77 +++----- src/storage/storage_tokens.cpp | 7 +- src/storage/storage_tokens.h | 30 ++++ src/storage/storage_users.cpp | 2 +- src/storage/storage_users.h | 14 -- wwwassets/password_reset.html | 52 ++++-- 34 files changed, 862 insertions(+), 272 deletions(-) create mode 100644 src/ActionLinkManager.cpp create mode 100644 src/ActionLinkManager.h create mode 100644 src/storage/storage_actionLinks.cpp create mode 100644 src/storage/storage_actionLinks.h create mode 100644 src/storage/storage_tokens.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 16c01d1..8f1c067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.13) -project(owsec VERSION 2.3.0) +project(owsec VERSION 2.4.0) set(CMAKE_CXX_STANDARD 17) @@ -86,7 +86,8 @@ add_executable( owsec src/MFAServer.cpp src/MFAServer.h src/SMS_provider_aws.cpp src/SMS_provider_aws.h src/SMS_provider.cpp src/SMS_provider.h - src/SMS_provider_twilio.cpp src/SMS_provider_twilio.h) + src/SMS_provider_twilio.cpp src/SMS_provider_twilio.h src/storage/storage_actionLinks.cpp src/storage/storage_actionLinks.h src/storage/storage_tokens.h src/ActionLinkManager.cpp src/ActionLinkManager.h + ) if(NOT SMALL_BUILD) target_link_libraries(owsec PUBLIC diff --git a/build b/build index 25bf17f..9d60796 100644 --- a/build +++ b/build @@ -1 +1 @@ -18 \ No newline at end of file +11 \ No newline at end of file diff --git a/src/ActionLinkManager.cpp b/src/ActionLinkManager.cpp new file mode 100644 index 0000000..879ea54 --- /dev/null +++ b/src/ActionLinkManager.cpp @@ -0,0 +1,64 @@ +// +// Created by stephane bourque on 2021-11-08. +// + +#include "ActionLinkManager.h" +#include "StorageService.h" + +namespace OpenWifi { + + int ActionLinkManager::Start() { + if(!Running_) + Thr_.start(*this); + + return 0; + } + + void ActionLinkManager::Stop() { + if(Running_) { + Running_ = false; + Thr_.wakeUp(); + Thr_.join(); + } + } + + void ActionLinkManager::run() { + Running_ = true ; + + while(Running_) { + Poco::Thread::trySleep(2000); + + if(!Running_) + break; + + std::vector Links; + { + std::lock_guard G(Mutex_); + Storage().GetActions(Links); + } + + if(Links.empty()) + continue; + + for(auto &i:Links) { + if(!Running_) + break; + + if(i.action=="forgot_password") { + if(AuthService::SendEmailToUser(i.id, i.userId, AuthService::FORGOT_PASSWORD)) { + Logger_.information(Poco::format("Send password reset link to %s",i.userId)); + } + Storage().SentAction(i.id); + } else if (i.action=="email_verification") { + if(AuthService::SendEmailToUser(i.id, i.userId, AuthService::EMAIL_VERIFICATION)) { + Logger_.information(Poco::format("Send password reset link to %s",i.userId)); + } + Storage().SentAction(i.id); + } else { + Storage().SentAction(i.id); + } + } + } + } + +} \ No newline at end of file diff --git a/src/ActionLinkManager.h b/src/ActionLinkManager.h new file mode 100644 index 0000000..f4770f4 --- /dev/null +++ b/src/ActionLinkManager.h @@ -0,0 +1,35 @@ +// +// Created by stephane bourque on 2021-11-08. +// + +#ifndef OWSEC_ACTIONLINKMANAGER_H +#define OWSEC_ACTIONLINKMANAGER_H + +#include "framework/MicroService.h" + +namespace OpenWifi { + + class ActionLinkManager : public SubSystemServer, Poco::Runnable { + public: + static ActionLinkManager * instance() { + static ActionLinkManager instance; + return &instance; + } + + int Start() final; + void Stop() final; + void run(); + + private: + Poco::Thread Thr_; + std::atomic_bool Running_ = false; + + ActionLinkManager() noexcept: + SubSystemServer("ActionLinkManager", "ACTION-SVR", "action.server") + { + } + }; + inline ActionLinkManager * ActionLinkManager() { return ActionLinkManager::instance(); } +} + +#endif //OWSEC_ACTIONLINKMANAGER_H diff --git a/src/AuthService.cpp b/src/AuthService.cpp index d743d33..e07d9e1 100644 --- a/src/AuthService.cpp +++ b/src/AuthService.cpp @@ -11,6 +11,7 @@ #include "Poco/Net/OAuth20Credentials.h" #include "Poco/JWT/Token.h" #include "Poco/JWT/Signer.h" +#include "Poco/StringTokenizer.h" #include "framework/MicroService.h" #include "StorageService.h" @@ -21,7 +22,6 @@ #include "MFAServer.h" namespace OpenWifi { - class AuthService *AuthService::instance_ = nullptr; AuthService::ACCESS_TYPE AuthService::IntToAccessType(int C) { switch (C) { @@ -198,35 +198,81 @@ namespace OpenWifi { } bool AuthService::SetPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { - auto NewPasswordHash = ComputePasswordHash(UInfo.email, NewPassword); - for (auto const &i:UInfo.lastPasswords) { - if (i == NewPasswordHash) { - return false; + std::lock_guard G(Mutex_); + + Poco::toLowerInPlace(UInfo.email); + for (const auto &i:UInfo.lastPasswords) { + auto Tokens = Poco::StringTokenizer(i,"|"); + if(Tokens.count()==2) { + const auto & Salt = Tokens[0]; + for(const auto &j:UInfo.lastPasswords) { + auto OldTokens = Poco::StringTokenizer(j,"|"); + if(OldTokens.count()==2) { + SHA2_.update(Salt+NewPassword+UInfo.email); + if(OldTokens[1]==Utils::ToHex(SHA2_.digest())) + return false; + } + } + } else { + SHA2_.update(NewPassword+UInfo.email); + if(Tokens[0]==Utils::ToHex(SHA2_.digest())) + return false; } } + if(UInfo.lastPasswords.size()==HowManyOldPassword_) { UInfo.lastPasswords.erase(UInfo.lastPasswords.begin()); } - UInfo.lastPasswords.push_back(NewPasswordHash); - UInfo.currentPassword = NewPasswordHash; + + auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword); + UInfo.lastPasswords.push_back(NewHash); + UInfo.currentPassword = NewHash; UInfo.changePassword = false; return true; } + static std::string GetMeSomeSalt() { + auto start = std::chrono::high_resolution_clock::now(); + return std::to_string(start.time_since_epoch().count()); + } + + std::string AuthService::ComputeNewPasswordHash(const std::string &UserName, const std::string &Password) { + std::string UName = Poco::trim(Poco::toLower(UserName)); + auto Salt = GetMeSomeSalt(); + SHA2_.update(Salt + Password + UName ); + return Salt + "|" + Utils::ToHex(SHA2_.digest()); + } + + bool AuthService::ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword) { + std::lock_guard G(Mutex_); + + std::string UName = Poco::trim(Poco::toLower(UserName)); + auto Tokens = Poco::StringTokenizer(StoredPassword,"|"); + if(Tokens.count()==1) { + SHA2_.update(Password+UName); + if(Tokens[0]==Utils::ToHex(SHA2_.digest())) + return true; + } else if (Tokens.count()==2) { + SHA2_.update(Tokens[0]+Password+UName); + if(Tokens[1]==Utils::ToHex(SHA2_.digest())) + return true; + } + return false; + } + AuthService::AUTH_ERROR AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo ) { std::lock_guard Guard(Mutex_); SecurityObjects::AclTemplate ACL; Poco::toLowerInPlace(UserName); - auto PasswordHash = ComputePasswordHash(UserName, Password); if(StorageService()->GetUserByEmail(UserName,UInfo.userinfo)) { if(UInfo.userinfo.waitingForEmailCheck) { return USERNAME_PENDING_VERIFICATION; } - if(PasswordHash != UInfo.userinfo.currentPassword) { + if(!ValidatePasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) { return INVALID_CREDENTIALS; } @@ -256,7 +302,7 @@ namespace OpenWifi { return SUCCESS; } - if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_) + if(((UserName == DefaultUserName_) && (ValidatePasswordHash(UserName,Password,DefaultPassword_))) || !Secure_) { ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true; UInfo.webtoken.acl_template_ = ACL; @@ -270,13 +316,7 @@ namespace OpenWifi { return INVALID_CREDENTIALS; } - std::string AuthService::ComputePasswordHash(const std::string &UserName, const std::string &Password) { - std::string UName = Poco::trim(Poco::toLower(UserName)); - SHA2_.update(Password + UName); - return Utils::ToHex(SHA2_.digest()); - } - - bool AuthService::SendEmailToUser(std::string &Email, EMAIL_REASON Reason) { + bool AuthService::SendEmailToUser(std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { SecurityObjects::UserInfo UInfo; if(StorageService()->GetUserByEmail(Email,UInfo)) { @@ -287,7 +327,7 @@ namespace OpenWifi { Attrs[RECIPIENT_EMAIL] = UInfo.email; Attrs[LOGO] = GetLogoAssetURI(); Attrs[SUBJECT] = "Password reset link"; - Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + UInfo.Id ; + Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ; SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs); } break; @@ -297,7 +337,7 @@ namespace OpenWifi { Attrs[RECIPIENT_EMAIL] = UInfo.email; Attrs[LOGO] = GetLogoAssetURI(); Attrs[SUBJECT] = "EMail Address Verification"; - Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; + Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ; SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); UInfo.waitingForEmailCheck = true; } @@ -306,19 +346,20 @@ namespace OpenWifi { default: break; } + return true; } return false; } bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) { - MessageAttributes Attrs; + SecurityObjects::ActionLink A; - Attrs[RECIPIENT_EMAIL] = UInfo.email; - Attrs[LOGO] = GetLogoAssetURI(); - Attrs[SUBJECT] = "EMail Address Verification"; - Attrs[ACTION_LINK] = - MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; - SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); + A.action = EMailReasons[EMAIL_VERIFICATION]; + A.userId = UInfo.email; + A.id = MicroService::instance().CreateUUID(); + A.created = std::time(nullptr); + A.expires = A.created + 24*60*60; + Storage().CreateAction(A); UInfo.waitingForEmailCheck = true; return true; } diff --git a/src/AuthService.h b/src/AuthService.h index d52277d..70583c1 100644 --- a/src/AuthService.h +++ b/src/AuthService.h @@ -50,14 +50,14 @@ namespace OpenWifi{ EMAIL_VERIFICATION }; + inline static std::map EMailReasons{ {FORGOT_PASSWORD, "forgot_password"} , { EMAIL_VERIFICATION, "email_verification"}}; + static ACCESS_TYPE IntToAccessType(int C); static int AccessTypeToInt(ACCESS_TYPE T); static AuthService *instance() { - if (instance_ == nullptr) { - instance_ = new AuthService; - } - return instance_; + static AuthService instance; + return &instance; } int Start() override; @@ -76,12 +76,15 @@ namespace OpenWifi{ [[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); [[nodiscard]] std::string GenerateTokenJWT(const std::string & UserName, ACCESS_TYPE Type); [[nodiscard]] std::string GenerateTokenHMAC(const std::string & UserName, ACCESS_TYPE Type); - [[nodiscard]] std::string ComputePasswordHash(const std::string &UserName, const std::string &Password); + + [[nodiscard]] std::string ComputeNewPasswordHash(const std::string &UserName, const std::string &Password); + [[nodiscard]] bool ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword); + [[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); [[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName); [[nodiscard]] static bool VerifyEmail(SecurityObjects::UserInfo &UInfo); - [[nodiscard]] static bool SendEmailToUser(std::string &Email, EMAIL_REASON Reason); + [[nodiscard]] static bool SendEmailToUser(std::string &LinkId, std::string &Email, EMAIL_REASON Reason); [[nodiscard]] bool DeleteUserFromCache(const std::string &UserName); [[nodiscard]] bool RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo); @@ -94,7 +97,6 @@ namespace OpenWifi{ } private: - static AuthService *instance_; bool Secure_ = false ; std::string DefaultUserName_; std::string DefaultPassword_; diff --git a/src/Daemon.cpp b/src/Daemon.cpp index 1ac6b63..41f9d48 100644 --- a/src/Daemon.cpp +++ b/src/Daemon.cpp @@ -30,6 +30,7 @@ #include "SMTPMailerService.h" #include "AuthService.h" #include "SMSSender.h" +#include "ActionLinkManager.h" namespace OpenWifi { class Daemon *Daemon::instance_ = nullptr; @@ -44,7 +45,9 @@ namespace OpenWifi { SubSystemVec{ StorageService(), SMSSender(), + ActionLinkManager(), SMTPMailerService(), + RESTAPI_RateLimiter(), AuthService() }); } diff --git a/src/MFAServer.cpp b/src/MFAServer.cpp index 0d18061..1f69f2f 100644 --- a/src/MFAServer.cpp +++ b/src/MFAServer.cpp @@ -10,8 +10,6 @@ namespace OpenWifi { - class MFAServer * MFAServer::instance_ = nullptr; - int MFAServer::Start() { return 0; } diff --git a/src/MFAServer.h b/src/MFAServer.h index ba5d094..1e83f50 100644 --- a/src/MFAServer.h +++ b/src/MFAServer.h @@ -24,24 +24,21 @@ namespace OpenWifi { int Start() override; void Stop() override; static MFAServer *instance() { - if (instance_ == nullptr) { - instance_ = new MFAServer; - } - return instance_; + static MFAServer instance; + return &instance; } bool StartMFAChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, Poco::JSON::Object &Challenge); bool CompleteMFAChallenge(Poco::JSON::Object::Ptr &ChallengeResponse, SecurityObjects::UserInfoAndPolicy &UInfo); - bool MethodEnabled(const std::string &Method); + static bool MethodEnabled(const std::string &Method); bool ResendCode(const std::string &uuid); - bool SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge); + static bool SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge); static inline std::string MakeChallenge() { return std::to_string(rand() % 999999); } private: - static MFAServer * instance_; MFAChallengeCache Cache_; MFAServer() noexcept: SubSystemServer("MFServer", "MFA-SVR", "mfa") diff --git a/src/RESTAPI/RESTAPI_action_links.cpp b/src/RESTAPI/RESTAPI_action_links.cpp index 4d9c4df..fdba75c 100644 --- a/src/RESTAPI/RESTAPI_action_links.cpp +++ b/src/RESTAPI/RESTAPI_action_links.cpp @@ -11,15 +11,20 @@ #include "Daemon.h" namespace OpenWifi { + void RESTAPI_action_links::DoGet() { auto Action = GetParameter("action",""); auto Id = GetParameter("id",""); + SecurityObjects::ActionLink Link; + if(!Storage().GetActionLink(Id,Link)) + return DoReturnA404(); + if(Action=="password_reset") - return RequestResetPassword(Id); + return RequestResetPassword(Link); else if(Action=="email_verification") - return DoEmailVerification(Id); + return DoEmailVerification(Link); else return DoReturnA404(); } @@ -28,29 +33,37 @@ namespace OpenWifi { auto Action = GetParameter("action",""); auto Id = GetParameter("id",""); - Logger_.information(Poco::format("COMPLETE-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id)); + SecurityObjects::ActionLink Link; + if(!Storage().GetActionLink(Id,Link)) + return DoReturnA404(); + + Logger_.information(Poco::format("COMPLETE-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Link.userId)); if(Action=="password_reset") - CompleteResetPassword(Id); + return CompleteResetPassword(Link); else - DoReturnA404(); + return DoReturnA404(); } - void RESTAPI_action_links::RequestResetPassword(std::string &Id) { - Logger_.information(Poco::format("REQUEST-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id)); + void RESTAPI_action_links::RequestResetPassword(SecurityObjects::ActionLink &Link) { + Logger_.information(Poco::format("REQUEST-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Link.userId)); Poco::File FormFile{ Daemon()->AssetDir() + "/password_reset.html"}; - Types::StringPairVec FormVars{ {"UUID", Id}, + Types::StringPairVec FormVars{ {"UUID", Link.id}, {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}}; SendHTMLFileBack(FormFile,FormVars); } - void RESTAPI_action_links::CompleteResetPassword(std::string &Id) { + void RESTAPI_action_links::CompleteResetPassword(SecurityObjects::ActionLink &Link) { // form has been posted... RESTAPI_PartHandler PartHandler; Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler); if (!Form.empty()) { auto Password1 = Form.get("password1","bla"); auto Password2 = Form.get("password1","blu"); - Id = Form.get("id",""); + auto Id = Form.get("id",""); + + if(Id!=Link.id) + return DoReturnA404(); + if(Password1!=Password2 || !AuthService()->ValidatePassword(Password2) || !AuthService()->ValidatePassword(Password1)) { Poco::File FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; Types::StringPairVec FormVars{ {"UUID", Id}, @@ -62,7 +75,7 @@ namespace OpenWifi { } SecurityObjects::UserInfo UInfo; - if(!StorageService()->GetUserById(Id,UInfo)) { + if(!StorageService()->GetUserByEmail(Link.userId,UInfo)) { Poco::File FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; Types::StringPairVec FormVars{ {"UUID", Id}, {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}}; @@ -93,12 +106,12 @@ namespace OpenWifi { } } - void RESTAPI_action_links::DoEmailVerification(std::string &Id) { + void RESTAPI_action_links::DoEmailVerification(SecurityObjects::ActionLink &Link) { SecurityObjects::UserInfo UInfo; - Logger_.information(Poco::format("EMAIL-VERIFICATION(%s): For ID=%s", Request->clientAddress().toString(), Id)); - if (!StorageService()->GetUserById(Id, UInfo)) { - Types::StringPairVec FormVars{{"UUID", Id}, + Logger_.information(Poco::format("EMAIL-VERIFICATION(%s): For ID=%s", Request->clientAddress().toString(), Link.userId)); + if (!StorageService()->GetUserByEmail(Link.userId, UInfo)) { + Types::StringPairVec FormVars{{"UUID", Link.id}, {"ERROR_TEXT", "This does not appear to be a valid email verification link.."}}; Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_error.html"}; return SendHTMLFileBack(FormFile, FormVars); @@ -108,8 +121,8 @@ namespace OpenWifi { UInfo.validated = true; UInfo.lastEmailCheck = std::time(nullptr); UInfo.validationDate = std::time(nullptr); - StorageService()->UpdateUserInfo(UInfo.email, Id, UInfo); - Types::StringPairVec FormVars{{"UUID", Id}, + StorageService()->UpdateUserInfo(UInfo.email, Link.userId, UInfo); + Types::StringPairVec FormVars{{"UUID", Link.id}, {"USERNAME", UInfo.email}, {"ACTION_LINK",MicroService::instance().GetUIURI()}}; Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_success.html"}; diff --git a/src/RESTAPI/RESTAPI_action_links.h b/src/RESTAPI/RESTAPI_action_links.h index fe7138f..6a96d84 100644 --- a/src/RESTAPI/RESTAPI_action_links.h +++ b/src/RESTAPI/RESTAPI_action_links.h @@ -19,11 +19,12 @@ namespace OpenWifi { Poco::Net::HTTPRequest::HTTP_OPTIONS}, Server, Internal, - false) {} + false, + true) {} static const std::list PathName() { return std::list{"/api/v1/actionLink"}; }; - void RequestResetPassword(std::string &Id); - void CompleteResetPassword(std::string &Id); - void DoEmailVerification(std::string &Id); + void RequestResetPassword(SecurityObjects::ActionLink &Link); + void CompleteResetPassword(SecurityObjects::ActionLink &Link); + void DoEmailVerification(SecurityObjects::ActionLink &Link); void DoReturnA404(); void DoGet() final; diff --git a/src/RESTAPI/RESTAPI_oauth2Handler.cpp b/src/RESTAPI/RESTAPI_oauth2Handler.cpp index 6cfee69..6d18164 100644 --- a/src/RESTAPI/RESTAPI_oauth2Handler.cpp +++ b/src/RESTAPI/RESTAPI_oauth2Handler.cpp @@ -14,6 +14,7 @@ #include "MFAServer.h" #include "framework/RESTAPI_protocol.h" #include "framework/MicroService.h" +#include "StorageService.h" namespace OpenWifi { void RESTAPI_oauth2Handler::DoGet() { @@ -65,11 +66,18 @@ namespace OpenWifi { if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) { // Send an email to the userId Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", Request->clientAddress().toString(), userId)); - SecurityObjects::UserInfoAndPolicy UInfo; - if(AuthService::SendEmailToUser(userId,AuthService::FORGOT_PASSWORD)) - Logger_.information(Poco::format("Send password reset link to %s",userId)); - UInfo.webtoken.userMustChangePassword=true; + SecurityObjects::ActionLink NewLink; + + NewLink.action = AuthService::EMailReasons[AuthService::FORGOT_PASSWORD]; + NewLink.id = MicroService::instance().CreateUUID(); + NewLink.userId = userId; + NewLink.created = std::time(nullptr); + NewLink.expires = NewLink.created + (24*60*60); + Storage().CreateAction(NewLink); + Poco::JSON::Object ReturnObj; + SecurityObjects::UserInfoAndPolicy UInfo; + UInfo.webtoken.userMustChangePassword = true; UInfo.webtoken.to_json(ReturnObj); return ReturnObject(ReturnObj); } diff --git a/src/RESTAPI/RESTAPI_oauth2Handler.h b/src/RESTAPI/RESTAPI_oauth2Handler.h index 38e694c..8b0a908 100644 --- a/src/RESTAPI/RESTAPI_oauth2Handler.h +++ b/src/RESTAPI/RESTAPI_oauth2Handler.h @@ -21,7 +21,7 @@ namespace OpenWifi { Poco::Net::HTTPRequest::HTTP_GET, Poco::Net::HTTPRequest::HTTP_OPTIONS}, Server, - Internal, false) {} + Internal, false, true) {} static const std::list PathName() { return std::list{"/api/v1/oauth2/{token}","/api/v1/oauth2"}; }; void DoGet() final; void DoPost() final; diff --git a/src/RESTAPI/RESTAPI_user_handler.cpp b/src/RESTAPI/RESTAPI_user_handler.cpp index e912037..b9f0c53 100644 --- a/src/RESTAPI/RESTAPI_user_handler.cpp +++ b/src/RESTAPI/RESTAPI_user_handler.cpp @@ -25,7 +25,11 @@ namespace OpenWifi { } else if(!StorageService()->GetUserById(Id,UInfo)) { return NotFound(); } + Poco::JSON::Object UserInfoObject; + UInfo.currentPassword.clear(); + UInfo.lastPasswords.clear(); + UInfo.oauthType.clear(); UInfo.to_json(UserInfoObject); ReturnObject(UserInfoObject); } @@ -45,8 +49,9 @@ namespace OpenWifi { return NotFound(); } - if(AuthService()->DeleteUserFromCache(UInfo.email)) - ; + if(AuthService()->DeleteUserFromCache(UInfo.email)) { + // nothing to do + } Logger_.information(Poco::format("Remove all tokens for '%s'", UserInfo_.userinfo.email)); StorageService()->RevokeAllTokens(UInfo.email); Logger_.information(Poco::format("User '%s' deleted by '%s'.",Id,UserInfo_.userinfo.email)); diff --git a/src/RESTAPI/RESTAPI_users_handler.cpp b/src/RESTAPI/RESTAPI_users_handler.cpp index 9bfe636..9ce3b43 100644 --- a/src/RESTAPI/RESTAPI_users_handler.cpp +++ b/src/RESTAPI/RESTAPI_users_handler.cpp @@ -16,11 +16,14 @@ namespace OpenWifi { Poco::JSON::Array ArrayObj; Poco::JSON::Object Answer; if (StorageService()->GetUsers(QB_.Offset, QB_.Limit, Users)) { - for (const auto &i : Users) { + for (auto &i : Users) { Poco::JSON::Object Obj; if (IdOnly) { ArrayObj.add(i.Id); } else { + i.currentPassword.clear(); + i.lastPasswords.clear(); + i.oauthType.clear(); i.to_json(Obj); ArrayObj.add(Obj); } @@ -38,6 +41,9 @@ namespace OpenWifi { if (IdOnly) { ArrayObj.add(UInfo.Id); } else { + UInfo.currentPassword.clear(); + UInfo.lastPasswords.clear(); + UInfo.oauthType.clear(); UInfo.to_json(Obj); ArrayObj.add(Obj); } diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.cpp b/src/RESTObjects/RESTAPI_SecurityObjects.cpp index 93d8a97..14062b2 100644 --- a/src/RESTObjects/RESTAPI_SecurityObjects.cpp +++ b/src/RESTObjects/RESTAPI_SecurityObjects.cpp @@ -138,7 +138,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"primary", primary); } - bool MobilePhoneNumber::from_json(Poco::JSON::Object::Ptr Obj) { + bool MobilePhoneNumber::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"number",number); field_from_json(Obj,"verified",verified); @@ -155,7 +155,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"method", method); } - bool MfaAuthInfo::from_json(Poco::JSON::Object::Ptr Obj) { + bool MfaAuthInfo::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"enabled",enabled); field_from_json(Obj,"method",method); @@ -171,7 +171,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj, "mfa", mfa); } - bool UserLoginLoginExtensions::from_json(Poco::JSON::Object::Ptr Obj) { + bool UserLoginLoginExtensions::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"mobiles",mobiles); field_from_json(Obj,"mfa",mfa); @@ -189,7 +189,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj, "method", method); } - bool MFAChallengeRequest::from_json(Poco::JSON::Object::Ptr Obj) { + bool MFAChallengeRequest::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"uuid",uuid); field_from_json(Obj,"question",question); @@ -208,7 +208,7 @@ namespace OpenWifi::SecurityObjects { } - bool MFAChallengeResponse::from_json(Poco::JSON::Object::Ptr Obj) { + bool MFAChallengeResponse::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"uuid",uuid); field_from_json(Obj,"answer",answer); @@ -387,11 +387,12 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"note", note); } - bool NoteInfo::from_json(Poco::JSON::Object::Ptr Obj) { + bool NoteInfo::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"created",created); field_from_json(Obj,"createdBy",createdBy); field_from_json(Obj,"note",note); + return true; } catch(...) { } @@ -428,10 +429,11 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"access", access, ResourceAccessTypeToString); } - bool ProfileAction::from_json(Poco::JSON::Object::Ptr Obj) { + bool ProfileAction::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"resource",resource); field_from_json(Obj,"access",access,ResourceAccessTypeFromString ); + return true; } catch(...) { } @@ -447,7 +449,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"notes", notes); } - bool SecurityProfile::from_json(Poco::JSON::Object::Ptr Obj) { + bool SecurityProfile::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"id",id); field_from_json(Obj,"name",name); @@ -455,6 +457,7 @@ namespace OpenWifi::SecurityObjects { field_from_json(Obj,"policy",policy); field_from_json(Obj,"role",role); field_from_json(Obj,"notes",notes); + return true; } catch(...) { } @@ -465,13 +468,51 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj, "profiles", profiles); } - bool SecurityProfileList::from_json(Poco::JSON::Object::Ptr Obj) { + bool SecurityProfileList::from_json(Poco::JSON::Object::Ptr &Obj) { try { field_from_json(Obj,"profiles",profiles); + return true; } catch(...) { } return false; } + + void ActionLink::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id",id); + field_to_json(Obj,"action",action); + field_to_json(Obj,"userId",userId); + field_to_json(Obj,"actionTemplate",actionTemplate); + field_to_json(Obj,"variables",variables); + field_to_json(Obj,"locale",locale); + field_to_json(Obj,"message",message); + field_to_json(Obj,"sent",sent); + field_to_json(Obj,"created",created); + field_to_json(Obj,"expires",expires); + field_to_json(Obj,"completed",completed); + field_to_json(Obj,"canceled",canceled); + + } + + bool ActionLink::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id",id); + field_from_json(Obj,"action",action); + field_from_json(Obj,"userId",userId); + field_from_json(Obj,"actionTemplate",actionTemplate); + field_from_json(Obj,"variables",variables); + field_from_json(Obj,"locale",locale); + field_from_json(Obj,"message",message); + field_from_json(Obj,"sent",sent); + field_from_json(Obj,"created",created); + field_from_json(Obj,"expires",expires); + field_from_json(Obj,"completed",completed); + field_from_json(Obj,"canceled",canceled); + return true; + } catch(...) { + + } + return false; + } } diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.h b/src/RESTObjects/RESTAPI_SecurityObjects.h index 232534f..9e9b42c 100644 --- a/src/RESTObjects/RESTAPI_SecurityObjects.h +++ b/src/RESTObjects/RESTAPI_SecurityObjects.h @@ -10,7 +10,7 @@ #define UCENTRAL_RESTAPI_SECURITYOBJECTS_H #include "Poco/JSON/Object.h" -#include "../framework/OpenWifiTypes.h" +#include "framework/OpenWifiTypes.h" namespace OpenWifi::SecurityObjects { @@ -53,7 +53,7 @@ namespace OpenWifi::SecurityObjects { std::string createdBy; std::string note; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; typedef std::vector NoteInfoVec; @@ -63,7 +63,7 @@ namespace OpenWifi::SecurityObjects { bool primary; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; struct MfaAuthInfo { @@ -71,7 +71,7 @@ namespace OpenWifi::SecurityObjects { std::string method; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; struct UserLoginLoginExtensions { @@ -79,7 +79,7 @@ namespace OpenWifi::SecurityObjects { struct MfaAuthInfo mfa; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; struct MFAChallengeRequest { @@ -89,7 +89,7 @@ namespace OpenWifi::SecurityObjects { uint64_t created; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; struct MFAChallengeResponse { @@ -97,7 +97,7 @@ namespace OpenWifi::SecurityObjects { std::string answer; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; struct UserInfo { @@ -200,7 +200,7 @@ namespace OpenWifi::SecurityObjects { std::string resource; ResourceAccessType access; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; typedef std::vector ProfileActionVec; @@ -212,14 +212,32 @@ namespace OpenWifi::SecurityObjects { std::string role; NoteInfoVec notes; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); }; typedef std::vector SecurityProfileVec; struct SecurityProfileList { SecurityProfileVec profiles; void to_json(Poco::JSON::Object &Obj) const; - bool from_json(Poco::JSON::Object::Ptr Obj); + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct ActionLink { + std::string id; + std::string action; + std::string userId; + std::string actionTemplate; + Types::StringPairVec variables; + std::string locale; + std::string message; + uint64_t sent=0; + uint64_t created=std::time(nullptr); + uint64_t expires=0; + uint64_t completed=0; + uint64_t canceled=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); }; } diff --git a/src/SMSSender.cpp b/src/SMSSender.cpp index ee51374..93b8541 100644 --- a/src/SMSSender.cpp +++ b/src/SMSSender.cpp @@ -14,7 +14,6 @@ #include "framework/MicroService.h" namespace OpenWifi { - class SMSSender * SMSSender::instance_ = nullptr; int SMSSender::Start() { Provider_ = MicroService::instance().ConfigGetString("sms.provider","aws"); diff --git a/src/SMSSender.h b/src/SMSSender.h index 29dec7d..c493130 100644 --- a/src/SMSSender.h +++ b/src/SMSSender.h @@ -25,10 +25,8 @@ namespace OpenWifi { class SMSSender : public SubSystemServer { public: static SMSSender *instance() { - if (instance_ == nullptr) { - instance_ = new SMSSender; - } - return instance_; + static SMSSender instance; + return &instance; } int Start() final; @@ -39,7 +37,6 @@ namespace OpenWifi { bool IsNumberValid(const std::string &Number, const std::string &UserName); [[nodiscard]] bool Send(const std::string &PhoneNumber, const std::string &Message); private: - static SMSSender * instance_; std::string Provider_; bool Enabled_=false; std::vector Cache_; diff --git a/src/SMTPMailerService.cpp b/src/SMTPMailerService.cpp index 59c41fb..4b57b09 100644 --- a/src/SMTPMailerService.cpp +++ b/src/SMTPMailerService.cpp @@ -22,8 +22,6 @@ namespace OpenWifi { - class SMTPMailerService * SMTPMailerService::instance_ = nullptr; - void SMTPMailerService::LoadMyConfig() { MailHost_ = MicroService::instance().ConfigGetString("mailer.hostname"); SenderLoginUserName_ = MicroService::instance().ConfigGetString("mailer.username"); diff --git a/src/SMTPMailerService.h b/src/SMTPMailerService.h index 532a0d4..7261f0c 100644 --- a/src/SMTPMailerService.h +++ b/src/SMTPMailerService.h @@ -59,10 +59,8 @@ namespace OpenWifi { class SMTPMailerService : public SubSystemServer, Poco::Runnable { public: static SMTPMailerService *instance() { - if (instance_ == nullptr) { - instance_ = new SMTPMailerService; - } - return instance_; + static SMTPMailerService instance; + return & instance; } struct MessageEvent { @@ -88,7 +86,6 @@ namespace OpenWifi { void reinitialize(Poco::Util::Application &self) override; bool Enabled() const { return Enabled_; } private: - static SMTPMailerService * instance_; std::string MailHost_; std::string Sender_; int MailHostPort_=25; diff --git a/src/StorageService.cpp b/src/StorageService.cpp index 7313d59..be15de7 100644 --- a/src/StorageService.cpp +++ b/src/StorageService.cpp @@ -10,8 +10,6 @@ namespace OpenWifi { - class Storage *Storage::instance_ = nullptr; - int Storage::Start() { std::lock_guard Guard(Mutex_); diff --git a/src/StorageService.h b/src/StorageService.h index 444189f..e5cd268 100644 --- a/src/StorageService.h +++ b/src/StorageService.h @@ -15,34 +15,6 @@ namespace OpenWifi { - static const std::string AllActionLinksFieldsForSelect { - "Id, " - "Action," - "UserId," - "template," - "locale," - "message," - "sent," - "created," - "expires," - "completed," - "canceled" - }; - - static const std::string AllActionLinksFieldsForUpdate { - "Id=?, " - "Action=?," - "UserId=?," - "template=?," - "locale=?," - "message=?," - "sent=?," - "created=?," - "expires=?," - "completed=?," - "canceled=?" - }; - static const std::string AllEmailTemplatesFieldsForCreation { }; @@ -90,7 +62,7 @@ namespace OpenWifi { return UNKNOWN; } - static const std::string from_userType(USER_TYPE U) { + static std::string from_userType(USER_TYPE U) { switch(U) { case ROOT: return "root"; case ADMIN: return "admin"; @@ -104,10 +76,8 @@ namespace OpenWifi { } static Storage *instance() { - if (instance_ == nullptr) { - instance_ = new Storage; - } - return instance_; + static Storage instance; + return &instance; } int Start() override; @@ -143,18 +113,20 @@ namespace OpenWifi { /* * All ActionLinks functions */ - bool CreateAction(std::string &ActionId, std::string &Action, USER_ID_TYPE & Id, Types::StringPairVec & Elements ); + bool CreateAction( SecurityObjects::ActionLink & A); bool DeleteAction(std::string &ActionId); bool CompleteAction(std::string &ActionId); bool CancelAction(std::string &ActionId); + bool SentAction(std::string &ActionId); + bool GetActionLink(std::string &ActionId, SecurityObjects::ActionLink &A); + bool GetActions(std::vector &Links, uint64_t Max=200); private: - static Storage *instance_; - int Create_Tables(); int Create_UserTable(); int Create_AvatarTable(); int Create_TokensTable(); + int Create_ActionLinkTable(); }; inline Storage * StorageService() { return Storage::instance(); }; diff --git a/src/framework/MicroService.h b/src/framework/MicroService.h index 59e751d..56c43a3 100644 --- a/src/framework/MicroService.h +++ b/src/framework/MicroService.h @@ -57,6 +57,7 @@ using namespace std::chrono_literals; #include "Poco/URI.h" #include "Poco/Net/HTTPSClientSession.h" #include "Poco/Net/NetworkInterface.h" +#include "Poco/ExpireLRUCache.h" #include "cppkafka/cppkafka.h" @@ -263,6 +264,21 @@ namespace OpenWifi::RESTAPI_utils { return OS.str(); } + inline std::string to_string(const Types::StringPairVec & ObjectArray) { + Poco::JSON::Array OutputArr; + if(ObjectArray.empty()) + return "[]"; + for(auto const &i:ObjectArray) { + Poco::JSON::Array InnerArray; + InnerArray.add(i.first); + InnerArray.add(i.second); + OutputArr.add(InnerArray); + } + std::ostringstream OS; + Poco::JSON::Stringifier::condense(OutputArr,OS); + return OS.str(); + } + template std::string to_string(const std::vector & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) @@ -345,6 +361,27 @@ namespace OpenWifi::RESTAPI_utils { return Result; } + inline Types::StringPairVec to_stringpair_array(const std::string &S) { + Types::StringPairVec R; + if(S.empty()) + return R; + try { + Poco::JSON::Parser P; + auto Object = P.parse(S).template extract(); + for (auto const &i : *Object) { + auto InnerObject = i.template extract(); + if(InnerObject->size()==2) { + Types::StringPair P{InnerObject->get(0).toString(), InnerObject->get(1).toString()}; + R.push_back(P); + } + } + } catch (...) { + + } + + return R; + } + template std::vector to_object_array(const std::string & ObjectString) { std::vector Result; if(ObjectString.empty()) @@ -1309,6 +1346,63 @@ namespace OpenWifi { std::string _fileName; }; + class RESTAPI_RateLimiter : public SubSystemServer { + public: + + struct ClientCacheEntry { + int64_t Start=0; + int Count=0; + }; + + static RESTAPI_RateLimiter *instance() { + static RESTAPI_RateLimiter instance; + return &instance; + } + + inline int Start() final { return 0;}; + inline void Stop() final { }; + + inline bool IsRateLimited(const Poco::Net::HTTPServerRequest &R, int64_t Period, int64_t MaxCalls) { + Poco::URI uri(R.getURI()); + auto H = str_hash(uri.getPath() + R.clientAddress().host().toString()); + auto E = Cache_.get(H); + const auto p1 = std::chrono::system_clock::now(); + auto Now = std::chrono::duration_cast(p1.time_since_epoch()).count(); + if(E.isNull()) { + Cache_.add(H,ClientCacheEntry{.Start=Now, .Count=1}); + Logger_.warning(Poco::format("RATE-LIMIT-EXCEEDED: from '%s'", R.clientAddress().toString())); + return false; + } + if((Now-E->Start)Count++; + Cache_.update(H,E); + if(E->Count > MaxCalls) + return true; + return false; + } + E->Start = Now; + E->Count = 1; + Cache_.update(H,E); + return false; + } + + inline void Clear() { + Cache_.clear(); + } + + private: + Poco::ExpireLRUCache Cache_{2048}; + std::hash str_hash; + + RESTAPI_RateLimiter() noexcept: + SubSystemServer("RateLimiter", "RATE-LIMITER", "rate.limiter") + { + } + + }; + + inline RESTAPI_RateLimiter * RESTAPI_RateLimiter() { return RESTAPI_RateLimiter::instance(); } + class RESTAPIHandler : public Poco::Net::HTTPRequestHandler { public: struct QueryBlock { @@ -1318,8 +1412,28 @@ namespace OpenWifi { }; typedef std::map BindingMap; - RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector Methods, RESTAPI_GenericServer & Server, bool Internal=false, bool AlwaysAuthorize=true) - : Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), Server_(Server), Internal_(Internal), AlwaysAuthorize_(AlwaysAuthorize) {} + struct RateLimit { + int64_t Interval=1000; + int64_t MaxCalls=100; + }; + + RESTAPIHandler( BindingMap map, + Poco::Logger &l, + std::vector Methods, + RESTAPI_GenericServer & Server, + bool Internal=false, + bool AlwaysAuthorize=true, + bool RateLimited=false, + const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100}) + : Bindings_(std::move(map)), + Logger_(l), + Methods_(std::move(Methods)), + Server_(Server), + Internal_(Internal), + AlwaysAuthorize_(AlwaysAuthorize), + RateLimited_(RateLimited), + MyRates_(Profile){ + } inline bool RoleIsAuthorized(const std::string & Path, const std::string & Method, std::string & Reason) { return true; @@ -1331,6 +1445,9 @@ namespace OpenWifi { Request = &RequestIn; Response = &ResponseIn; + if(RateLimited_ && RESTAPI_RateLimiter()->IsRateLimited(RequestIn,MyRates_.Interval, MyRates_.MaxCalls)) + return; + if (!ContinueProcessing()) return; @@ -1760,12 +1877,14 @@ namespace OpenWifi { std::vector Methods_; QueryBlock QB_; bool Internal_=false; + bool RateLimited_=false; bool QueryBlockInitialized_=false; Poco::Net::HTTPServerRequest *Request= nullptr; Poco::Net::HTTPServerResponse *Response= nullptr; bool AlwaysAuthorize_=true; Poco::JSON::Parser IncomingParser_; RESTAPI_GenericServer & Server_; + RateLimit MyRates_; }; class RESTAPI_UnknownRequestHandler : public RESTAPIHandler { @@ -1890,9 +2009,8 @@ namespace OpenWifi { inline void initialize(Poco::Util::Application & self) override; static KafkaManager *instance() { - if(instance_== nullptr) - instance_ = new KafkaManager; - return instance_; + static KafkaManager instance; + return &instance; } inline int Start() override { @@ -1967,7 +2085,6 @@ namespace OpenWifi { // void WakeUp(); private: - static KafkaManager *instance_; std::mutex ProducerMutex_; std::mutex ConsumerMutex_; bool KafkaEnabled_ = false; @@ -1995,7 +2112,6 @@ namespace OpenWifi { }; inline KafkaManager * KafkaManager() { return KafkaManager::instance(); } - inline class KafkaManager *KafkaManager::instance_ = nullptr; class AuthClient : public SubSystemServer { public: @@ -2005,10 +2121,8 @@ namespace OpenWifi { } static AuthClient *instance() { - if (instance_ == nullptr) { - instance_ = new AuthClient; - } - return instance_; + static AuthClient instance; + return &instance; } inline int Start() override { @@ -2086,12 +2200,10 @@ namespace OpenWifi { } private: - static AuthClient *instance_; OpenWifi::SecurityObjects::UserInfoCache UserCache_; }; inline AuthClient * AuthClient() { return AuthClient::instance(); } - inline class AuthClient * AuthClient::instance_ = nullptr; class ALBRequestHandler: public Poco::Net::HTTPRequestHandler /// Return a HTML document with the current date and time. @@ -2148,10 +2260,8 @@ namespace OpenWifi { } static ALBHealthCheckServer *instance() { - if (instance_ == nullptr) { - instance_ = new ALBHealthCheckServer; - } - return instance_; + static ALBHealthCheckServer instance; + return &instance; } inline int Start() override; @@ -2162,14 +2272,12 @@ namespace OpenWifi { } private: - static ALBHealthCheckServer *instance_; std::unique_ptr Server_; std::unique_ptr Socket_; int Port_ = 0; }; inline ALBHealthCheckServer * ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } - inline class ALBHealthCheckServer * ALBHealthCheckServer::instance_ = nullptr; Poco::Net::HTTPRequestHandler * RESTAPI_external_server(const char *Path, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & L, RESTAPI_GenericServer & S); @@ -2181,10 +2289,8 @@ namespace OpenWifi { class RESTAPI_server : public SubSystemServer { public: static RESTAPI_server *instance() { - if (instance_ == nullptr) { - instance_ = new RESTAPI_server; - } - return instance_; + static RESTAPI_server instance; + return &instance; } int Start() override; inline void Stop() override { @@ -2202,7 +2308,6 @@ namespace OpenWifi { } private: - static RESTAPI_server *instance_; std::vector> RESTServers_; Poco::ThreadPool Pool_; RESTAPI_GenericServer Server_; @@ -2235,9 +2340,6 @@ namespace OpenWifi { RESTAPI_GenericServer &Server_; }; - - inline class RESTAPI_server *RESTAPI_server::instance_ = nullptr; - inline int RESTAPI_server::Start() { Logger_.information("Starting."); Server_.InitLogging(); @@ -2269,10 +2371,8 @@ namespace OpenWifi { public: static RESTAPI_InternalServer *instance() { - if (instance_ == nullptr) { - instance_ = new RESTAPI_InternalServer; - } - return instance_; + static RESTAPI_InternalServer instance; + return &instance; } inline int Start() override; @@ -2290,7 +2390,6 @@ namespace OpenWifi { return RESTAPI_internal_server(Path, Bindings, Logger_, Server_); } private: - static RESTAPI_InternalServer *instance_; std::vector> RESTServers_; Poco::ThreadPool Pool_; RESTAPI_GenericServer Server_; @@ -2301,8 +2400,6 @@ namespace OpenWifi { }; - inline class RESTAPI_InternalServer* RESTAPI_InternalServer::instance_ = nullptr; - inline RESTAPI_InternalServer * RESTAPI_InternalServer() { return RESTAPI_InternalServer::instance(); }; class InternalRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { diff --git a/src/storage/storage_actionLinks.cpp b/src/storage/storage_actionLinks.cpp new file mode 100644 index 0000000..a069011 --- /dev/null +++ b/src/storage/storage_actionLinks.cpp @@ -0,0 +1,186 @@ +// +// Created by stephane bourque on 2021-11-08. +// + +#include "storage_actionLinks.h" + +#include + +#include "StorageService.h" +#include "RESTObjects/RESTAPI_SecurityObjects.h" +#include "framework/MicroService.h" + +namespace OpenWifi { + + bool Convert(const ActionLinkRecord &T, SecurityObjects::ActionLink &U) { + U.id = T.get<0>(); + U.action = T.get<1>(); + U.userId = T.get<2>(); + U.actionTemplate = T.get<3>(); + U.variables = RESTAPI_utils::to_stringpair_array(T.get<4>()); + U.locale = T.get<5>(); + U.message = T.get<6>(); + U.sent = T.get<7>(); + U.created = T.get<8>(); + U.expires = T.get<9>(); + U.completed = T.get<10>(); + U.canceled = T.get<11>(); + return true; + } + + bool Convert(const SecurityObjects::ActionLink &U, ActionLinkRecord &T) { + T.set<0>(U.id); + T.set<1>(U.action); + T.set<2>(U.userId); + T.set<3>(U.actionTemplate); + T.set<4>(RESTAPI_utils::to_string(U.variables)); + T.set<5>(U.locale); + T.set<6>(U.message); + T.set<7>(U.sent); + T.set<8>(U.created); + T.set<9>(U.expires); + T.set<10>(U.completed); + T.set<11>(U.canceled); + return true; + } + + bool Storage::CreateAction( SecurityObjects::ActionLink & A) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + std::string St2{ + "INSERT INTO ActionLinks (" + AllActionLinksFieldsForSelect + ") VALUES(" + AllActionLinksValuesForSelect + ")"}; + ActionLinkRecord AR; + Convert(A, AR); + Insert << ConvertParams(St2), + Poco::Data::Keywords::use(AR); + Insert.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::GetActions(std::vector &Links, uint64_t Max) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + + ActionLinkRecordList ARL; + + std::string St2{ + "SELECT " + AllActionLinksFieldsForSelect + " From ActionLinks where sent=0 and canceled=0"}; + Select << ConvertParams(St2), + Poco::Data::Keywords::into(ARL); + Select.execute(); + + for(const auto &i:ARL) { + SecurityObjects::ActionLink L; + Convert(i,L); + Links.emplace_back(L); + } + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::GetActionLink(std::string &ActionId, SecurityObjects::ActionLink &A) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + + ActionLinkRecord AR; + + std::string St2{ + "SELECT " + AllActionLinksFieldsForSelect + " From ActionLinks where id=?"}; + Select << ConvertParams(St2), + Poco::Data::Keywords::into(AR), + Poco::Data::Keywords::use(ActionId); + Select.execute(); + + if(Select.rowsExtracted()==1) { + Convert(AR, A); + return true; + } + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::SentAction(std::string &ActionId) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + uint64_t Sent = std::time(nullptr); + std::string St{"UPDATE ActionLinks set Sent=? where id=?"}; + Update << ConvertParams(St), + Poco::Data::Keywords::use(Sent), + Poco::Data::Keywords::use(ActionId); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::DeleteAction(std::string &ActionId) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Delete(Sess); + + uint64_t Sent = std::time(nullptr); + std::string St{"DELETE FROM ActionLinks where id=?"}; + Delete << ConvertParams(St), + Poco::Data::Keywords::use(ActionId); + Delete.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::CompleteAction(std::string &ActionId) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + uint64_t completed = std::time(nullptr); + std::string St{"UPDATE ActionLinks set completed=? where id=?"}; + Update << ConvertParams(St), + Poco::Data::Keywords::use(completed), + Poco::Data::Keywords::use(ActionId); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::CancelAction(std::string &ActionId) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + uint64_t canceled = std::time(nullptr); + std::string St{"UPDATE ActionLinks set canceled=? where id=?"}; + Update << ConvertParams(St), + Poco::Data::Keywords::use(canceled), + Poco::Data::Keywords::use(ActionId); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + +} \ No newline at end of file diff --git a/src/storage/storage_actionLinks.h b/src/storage/storage_actionLinks.h new file mode 100644 index 0000000..17415cc --- /dev/null +++ b/src/storage/storage_actionLinks.h @@ -0,0 +1,79 @@ +// +// Created by stephane bourque on 2021-11-08. +// + +#ifndef OWSEC_STORAGE_ACTIONLINKS_H +#define OWSEC_STORAGE_ACTIONLINKS_H + +#include +#include +#include "Poco/Tuple.h" + +namespace OpenWifi { + static const std::string AllActionLinksFieldsForCreation{ + "Id varchar(36)," + "Action text," + "UserId text," + "template text," + "variables text," + "locale varchar," + "message text," + "sent bigint," + "created bigint," + "expires bigint," + "completed bigint," + "canceled bigint" + }; + + static const std::string AllActionLinksFieldsForSelect { + "Id, " + "Action," + "UserId," + "template," + "variables," + "locale," + "message," + "sent," + "created," + "expires," + "completed," + "canceled" + }; + + static const std::string AllActionLinksValuesForSelect{ "?,?,?,?,?,?,?,?,?,?,?,?" }; + + static const std::string AllActionLinksFieldsForUpdate { + "Id=?, " + "Action=?," + "UserId=?," + "template=?," + "variables=?," + "locale=?," + "message=?," + "sent=?," + "created=?," + "expires=?," + "completed=?," + "canceled=?" + }; + + typedef Poco::Tuple < + std::string, // id + std::string, // action + std::string, // userId + std::string, // actionTemplate + std::string, // variables + std::string, // locale + std::string, // message + uint64_t, // sent + uint64_t, // created + uint64_t, // expires + uint64_t, // completed + uint64_t // canceled + > ActionLinkRecord; + typedef std::vector ActionLinkRecordList; + +} + + +#endif //OWSEC_STORAGE_ACTIONLINKS_H diff --git a/src/storage/storage_avatar.cpp b/src/storage/storage_avatar.cpp index 47125d0..3ae7d38 100644 --- a/src/storage/storage_avatar.cpp +++ b/src/storage/storage_avatar.cpp @@ -35,8 +35,7 @@ namespace OpenWifi { uint64_t Now = std::time(nullptr); - std::string St2{ - "INSERT INTO Avatars (Id,Type,Created,Name,Avatar) VALUES(?,?,?,?,?)"}; + std::string St2{"INSERT INTO Avatars (" + AllAvatarFieldsForSelect + ") VALUES( " + AllAvatarValuesForSelect + " )"}; Insert << ConvertParams(St2), Poco::Data::Keywords::use(Id), @@ -58,13 +57,19 @@ namespace OpenWifi { Poco::Data::Session Sess = Pool_->get(); Poco::Data::Statement Select(Sess); - std::string St2{"SELECT Avatar, Type, Name FROM Avatars WHERE Id=?"}; + std::string St2{"SELECT " + AllAvatarFieldsForSelect + " FROM Avatars WHERE Id=?"}; Poco::Data::Statement Select2(Sess); + + std::string TId; + uint64_t Created; + Select2 << ConvertParams(St2), - Poco::Data::Keywords::into(L), + Poco::Data::Keywords::into(TId), Poco::Data::Keywords::into(Type), + Poco::Data::Keywords::into(Created), Poco::Data::Keywords::into(Name), + Poco::Data::Keywords::into(L), Poco::Data::Keywords::use(Id); Select2.execute(); diff --git a/src/storage/storage_avatar.h b/src/storage/storage_avatar.h index e7bdb4f..b6735f3 100644 --- a/src/storage/storage_avatar.h +++ b/src/storage/storage_avatar.h @@ -7,6 +7,33 @@ namespace OpenWifi { + static const std::string AllAvatarFieldsForCreation_sqlite{ + "Id VARCHAR(36) PRIMARY KEY, " + "Type VARCHAR, " + "Created BIGINT, " + "Name VARCHAR, " + "Avatar BLOB" + }; + + static const std::string AllAvatarFieldsForCreation_mysql{ + "Id VARCHAR(36) PRIMARY KEY, " + "Type VARCHAR, " + "Created BIGINT, " + "Name VARCHAR, " + "Avatar LONGBLOB" + }; + + static const std::string AllAvatarFieldsForCreation_pgsql{ + "Id VARCHAR(36) PRIMARY KEY, " + "Type VARCHAR, " + "Created BIGINT, " + "Name VARCHAR, " + "Avatar BYTEA" + }; + + static const std::string AllAvatarFieldsForSelect{ " Id,Type,Created,Name,Avatar " }; + static const std::string AllAvatarValuesForSelect{ "?,?,?,?,?" }; + } diff --git a/src/storage/storage_tables.cpp b/src/storage/storage_tables.cpp index fbf780d..1510512 100644 --- a/src/storage/storage_tables.cpp +++ b/src/storage/storage_tables.cpp @@ -5,6 +5,8 @@ #include "StorageService.h" #include "storage_users.h" #include "storage_avatar.h" +#include "storage_actionLinks.h" +#include "storage_tokens.h" namespace OpenWifi { @@ -12,6 +14,7 @@ namespace OpenWifi { Create_UserTable(); Create_AvatarTable(); Create_TokensTable(); + Create_ActionLinkTable(); return 0; } @@ -40,83 +43,51 @@ namespace OpenWifi { return 1; } + int Storage::Create_ActionLinkTable() { + try { + Poco::Data::Session Sess = Pool_->get(); + + Sess << "CREATE TABLE IF NOT EXISTS ActionLinks ( " + + AllActionLinksFieldsForCreation + " ) ", + Poco::Data::Keywords::now; + return 0; + } catch(const Poco::Exception &E) { + Logger_.log(E); + } + return 1; + } + int Storage::Create_AvatarTable() { try { Poco::Data::Session Sess = Pool_->get(); if(dbType_==sqlite) { - Sess << "CREATE TABLE IF NOT EXISTS Avatars (" - "Id VARCHAR(36) PRIMARY KEY, " - "Type VARCHAR, " - "Created BIGINT, " - "Name VARCHAR, " - "Avatar BLOB" + Sess << "CREATE TABLE IF NOT EXISTS Avatars (" + AllAvatarFieldsForCreation_sqlite + ") ", Poco::Data::Keywords::now; } else if(dbType_==mysql) { - Sess << "CREATE TABLE IF NOT EXISTS Avatars (" - "Id VARCHAR(36) PRIMARY KEY, " - "Type VARCHAR, " - "Created BIGINT, " - "Name VARCHAR, " - "Avatar LONGBLOB" + Sess << "CREATE TABLE IF NOT EXISTS Avatars (" + AllAvatarFieldsForCreation_mysql + ") ", Poco::Data::Keywords::now; } else if(dbType_==pgsql) { - Sess << "CREATE TABLE IF NOT EXISTS Avatars (" - "Id VARCHAR(36) PRIMARY KEY, " - "Type VARCHAR, " - "Created BIGINT, " - "Name VARCHAR, " - "Avatar BYTEA" + Sess << "CREATE TABLE IF NOT EXISTS Avatars (" + AllAvatarFieldsForCreation_pgsql + ") ", Poco::Data::Keywords::now; } return 0; } catch(const Poco::Exception &E) { Logger_.log(E); } - return 0; + return 1; } int Storage::Create_TokensTable() { try { Poco::Data::Session Sess = Pool_->get(); - if(dbType_==sqlite) { - Sess << "CREATE TABLE IF NOT EXISTS Tokens (" - "Token TEXT PRIMARY KEY, " - "RefreshToken TEXT, " - "TokenType TEXT, " - "UserName TEXT, " - "Created BIGINT, " - "Expires BIGINT, " - "IdleTimeOut BIGINT, " - "RevocationDate BIGINT " + Sess << "CREATE TABLE IF NOT EXISTS Tokens (" + + AllUsersFieldsForCreation + ") ", Poco::Data::Keywords::now; - } else if(dbType_==mysql) { - Sess << "CREATE TABLE IF NOT EXISTS Tokens (" - "Token TEXT PRIMARY KEY, " - "RefreshToken TEXT, " - "TokenType TEXT, " - "UserName TEXT, " - "Created BIGINT, " - "Expires BIGINT, " - "IdleTimeOut BIGINT, " - "RevocationDate BIGINT " - ") ", Poco::Data::Keywords::now; - } else if(dbType_==pgsql) { - Sess << "CREATE TABLE IF NOT EXISTS Tokens (" - "Token TEXT PRIMARY KEY, " - "RefreshToken TEXT, " - "TokenType TEXT, " - "UserName TEXT, " - "Created BIGINT, " - "Expires BIGINT, " - "IdleTimeOut BIGINT, " - "RevocationDate BIGINT " - ") ", Poco::Data::Keywords::now; - } return 0; } catch(const Poco::Exception &E) { Logger_.log(E); } - return 0; + return 1; } } diff --git a/src/storage/storage_tokens.cpp b/src/storage/storage_tokens.cpp index c9273a0..8f994a8 100644 --- a/src/storage/storage_tokens.cpp +++ b/src/storage/storage_tokens.cpp @@ -1,5 +1,6 @@ -#include "../StorageService.h" +#include "StorageService.h" +#include "storage/storage_tokens.h" namespace OpenWifi { @@ -22,7 +23,7 @@ namespace OpenWifi { uint64_t Z = 0; std::string St2{ - "INSERT INTO Tokens (Token, RefreshToken, TokenType, Username, Created, Expires, IdleTimeOut, RevocationDate) VALUES(?,?,?,?,?,?,?,?)"}; + "INSERT INTO Tokens (" + AllTokensFieldsForSelect + ") VALUES(" + AllTokensValuesForSelect + ")"}; Insert << ConvertParams(St2), Poco::Data::Keywords::use(Token), @@ -49,7 +50,7 @@ namespace OpenWifi { uint32_t RevocationDate = 0 ; - std::string St2{"SELECT Token, RefreshToken, TokenType, Username, Created, Expires, IdleTimeOut, RevocationDate From Tokens WHERE Token=?"}; + std::string St2{"SELECT " + AllTokensValuesForSelect + " From Tokens WHERE Token=?"}; Select << ConvertParams(St2), Poco::Data::Keywords::into(UInfo.webtoken.access_token_), Poco::Data::Keywords::into(UInfo.webtoken.refresh_token_), diff --git a/src/storage/storage_tokens.h b/src/storage/storage_tokens.h new file mode 100644 index 0000000..e4bda6c --- /dev/null +++ b/src/storage/storage_tokens.h @@ -0,0 +1,30 @@ +// +// Created by stephane bourque on 2021-11-08. +// + +#ifndef OWSEC_STORAGE_TOKENS_H +#define OWSEC_STORAGE_TOKENS_H + +#include +#include +#include "Poco/Tuple.h" + +namespace OpenWifi { + + static std::string AllTokensFieldsForCreation{ "Token TEXT PRIMARY KEY, " + "RefreshToken TEXT, " + "TokenType TEXT, " + "UserName TEXT, " + "Created BIGINT, " + "Expires BIGINT, " + "IdleTimeOut BIGINT, " + "RevocationDate BIGINT " + }; + static std::string AllTokensFieldsForSelect {"Token, RefreshToken, TokenType, Username, Created, Expires, IdleTimeOut, RevocationDate"}; + static std::string AllTokensValuesForSelect{"?,?,?,?,?,?,?,?"}; + + + +} + +#endif //OWSEC_STORAGE_TOKENS_H diff --git a/src/storage/storage_users.cpp b/src/storage/storage_users.cpp index 3717b0f..84690b3 100644 --- a/src/storage/storage_users.cpp +++ b/src/storage/storage_users.cpp @@ -111,7 +111,7 @@ namespace OpenWifi { if(NewUser.currentPassword.empty()) { } else { - NewUser.currentPassword = AuthService()->ComputePasswordHash(NewUser.email,NewUser.currentPassword); + NewUser.currentPassword = AuthService()->ComputeNewPasswordHash(NewUser.email,NewUser.currentPassword); NewUser.lastPasswords.clear(); NewUser.lastPasswords.push_back(NewUser.currentPassword); NewUser.lastPasswordChange = std::time(nullptr); diff --git a/src/storage/storage_users.h b/src/storage/storage_users.h index 3c26458..1beba8b 100644 --- a/src/storage/storage_users.h +++ b/src/storage/storage_users.h @@ -105,20 +105,6 @@ namespace OpenWifi { "oauthType=?, " "oauthUserInfo=? "}; - static const std::string AllActionLinksFieldsForCreation{ - "Id varchar(36)," - "Action varchar," - "UserId varchar," - "template varchar," - "locale varchar," - "message text," - "sent bigint," - "created bigint," - "expires bigint," - "completed bigint," - "canceled bigint" - }; - typedef Poco::Tuple < std::string, // Id = 0; std::string, // name; diff --git a/wwwassets/password_reset.html b/wwwassets/password_reset.html index 1dff0b1..d7d1ca0 100644 --- a/wwwassets/password_reset.html +++ b/wwwassets/password_reset.html @@ -3,8 +3,15 @@