diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f6390c..85e1dcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,18 +64,27 @@ add_executable( owsec src/RESTObjects/RESTAPI_ProvObjects.cpp src/RESTObjects/RESTAPI_ProvObjects.h src/RESTObjects/RESTAPI_GWobjects.h src/RESTObjects/RESTAPI_GWobjects.cpp src/RESTObjects/RESTAPI_FMSObjects.h src/RESTObjects/RESTAPI_FMSObjects.cpp - src/RESTAPI/RESTAPI_oauth2Handler.h src/RESTAPI/RESTAPI_oauth2Handler.cpp + src/RESTAPI/RESTAPI_oauth2_handler.h src/RESTAPI/RESTAPI_oauth2_handler.cpp src/RESTAPI/RESTAPI_users_handler.cpp src/RESTAPI/RESTAPI_users_handler.h src/RESTAPI/RESTAPI_user_handler.cpp src/RESTAPI/RESTAPI_user_handler.h src/RESTAPI/RESTAPI_action_links.cpp src/RESTAPI/RESTAPI_action_links.h - src/RESTAPI/RESTAPI_validateToken_handler.cpp src/RESTAPI/RESTAPI_validateToken_handler.h - src/RESTAPI/RESTAPI_systemEndpoints_handler.cpp src/RESTAPI/RESTAPI_systemEndpoints_handler.h - src/RESTAPI/RESTAPI_AssetServer.cpp src/RESTAPI/RESTAPI_AssetServer.h - src/RESTAPI/RESTAPI_avatarHandler.cpp src/RESTAPI/RESTAPI_avatarHandler.h + src/RESTAPI/RESTAPI_validate_token_handler.cpp src/RESTAPI/RESTAPI_validate_token_handler.h + src/RESTAPI/RESTAPI_system_endpoints_handler.cpp src/RESTAPI/RESTAPI_system_endpoints_handler.h + src/RESTAPI/RESTAPI_asset_server.cpp src/RESTAPI/RESTAPI_asset_server.h + src/RESTAPI/RESTAPI_avatar_handler.cpp src/RESTAPI/RESTAPI_avatar_handler.h src/RESTAPI/RESTAPI_email_handler.cpp src/RESTAPI/RESTAPI_email_handler.h src/RESTAPI/RESTAPI_sms_handler.cpp src/RESTAPI/RESTAPI_sms_handler.h - src/storage/storage_avatar.cpp src/storage/storage_avatar.h src/storage/storage_users.h - src/storage/storage_tables.cpp src/storage/storage_users.cpp src/storage/storage_tokens.cpp + src/storage/storage_avatar.cpp src/storage/storage_avatar.h + src/storage/storage_users.h src/storage/storage_users.cpp + src/storage/storage_actionLinks.cpp src/storage/storage_actionLinks.h + src/storage/storage_tokens.h src/storage/storage_tokens.cpp + src/storage/storage_subtokens.h src/storage/storage_subtokens.cpp + src/storage/storage_preferences.cpp src/storage/storage_preferences.h + src/storage/storage_subscribers.cpp src/storage/storage_subscribers.h + src/storage/storage_tables.cpp + src/RESTAPI/RESTAPI_suboauth2_handler.h src/RESTAPI/RESTAPI_suboauth2_handler.cpp + src/RESTAPI/RESTAPI_subuser_handler.h src/RESTAPI/RESTAPI_subuser_handler.cpp + src/RESTAPI/RESTAPI_subusers_handler.h src/RESTAPI/RESTAPI_subusers_handler.cpp src/APIServers.cpp src/Daemon.h src/Daemon.cpp src/AuthService.h src/AuthService.cpp @@ -86,10 +95,9 @@ add_executable( owsec 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/storage/storage_actionLinks.cpp src/storage/storage_actionLinks.h - src/storage/storage_tokens.h src/ActionLinkManager.cpp src/ActionLinkManager.h - src/ACLProcessor.h src/RESTAPI/RESTAPI_preferences.cpp src/RESTAPI/RESTAPI_preferences.h src/storage/storage_preferences.cpp src/storage/storage_preferences.h src/framework/OpenWifiTypes.h) + src/ACLProcessor.h src/RESTAPI/RESTAPI_preferences.cpp src/RESTAPI/RESTAPI_preferences.h + src/framework/OpenWifiTypes.h) if(NOT SMALL_BUILD) target_link_libraries(owsec PUBLIC diff --git a/build b/build index f11c82a..31ff414 100644 --- a/build +++ b/build @@ -1 +1 @@ -9 \ No newline at end of file +48 \ No newline at end of file diff --git a/openpapi/ucentralsec/owsec.yaml b/openpapi/ucentralsec/owsec.yaml index 5eb8bd5..3fa1232 100644 --- a/openpapi/ucentralsec/owsec.yaml +++ b/openpapi/ucentralsec/owsec.yaml @@ -730,6 +730,64 @@ paths: 404: $ref: '#/components/responses/NotFound' + /suboauth2: + post: + tags: + - Authentication + summary: Get access token - to be used as Bearer token header for all other API requests. + operationId: getAccessToken + parameters: + - in: query + name: newPassword + description: used when a user is trying to change her password. This will be the new password. + schema: + type: string + required: false + - in: query + name: forgotPassword + description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true + schema: + type: boolean + required: false + - in: query + name: requirements + description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true + schema: + type: boolean + required: false + - in: query + name: resendMFACode + schema: + type: boolean + required: false + - in: query + name: completeMFAChallenge + schema: + type: boolean + required: false + requestBody: + description: User id and password + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/WebTokenRequest' + - $ref: '#/components/schemas/MFAChallengeResponse' + responses: + 200: + description: successful operation + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/WebTokenResult' + - $ref: '#/components/schemas/MFAChallengeRequest' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /oauth2/{token}: delete: tags: @@ -755,6 +813,31 @@ paths: 404: $ref: '#/components/responses/NotFound' + /suboauth2/{token}: + delete: + tags: + - Authentication + summary: Revoke a token. + operationId: removeAccessToken + parameters: + - in: path + name: token + schema: + type: + string + required: true + responses: + 204: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/responses/Success' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /systemEndpoints: get: tags: @@ -819,6 +902,52 @@ paths: 404: $ref: '#/components/responses/NotFound' + /subusers: + get: + tags: + - User Management + summary: Retrieve a list of existing users as well as some information about them. + operationId: getUsers + parameters: + - in: query + name: offset + schema: + type: integer + format: int64 + required: false + - in: query + name: limit + schema: + type: integer + format: int64 + required: false + - in: query + description: Selecting this option means the newest record will be returned. Use limit to select how many. + name: filter + schema: + type: string + required: false + - in: query + description: Return only the ids. + name: idOnly + schema: + type: boolean + required: false + - in: query + description: Return only the ids. + name: select + schema: + type: string + example: id1,id2,id3,id4,id5 + required: false + responses: + 200: + $ref: '#/components/schemas/UserList' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /user/{id}: get: tags: @@ -923,6 +1052,110 @@ paths: 404: $ref: '#/components/responses/NotFound' + /subuser/{id}: + get: + tags: + - User Management + operationId: getUser + summary: Retrieve the information for a single user. + parameters: + - in: path + name: id + schema: + type: string + format: uuid + required: true + responses: + 200: + $ref: '#/components/schemas/UserInfo' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + + delete: + tags: + - User Management + operationId: deleteUser + summary: Delete a single user. + parameters: + - in: path + name: id + schema: + type: integer + format: int64 + required: true + responses: + 204: + $ref: '#/components/responses/Success' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + + post: + tags: + - User Management + operationId: createUser + summary: Create a single user. + parameters: + - in: path + name: id + #must be set to 0 for user creation + schema: + type: integer + format: int64 + required: true + - in: query + name: email_verification + schema: + type: boolean + required: false + requestBody: + description: User details (some fields are ignored during creation) + content: + application/json: + schema: + $ref: '#/components/schemas/UserInfo' + responses: + 200: + $ref: '#/components/schemas/UserInfo' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + + put: + tags: + - User Management + operationId: updateUser + summary: Modify a single user. + parameters: + - in: path + name: id + schema: + type: integer + format: int64 + required: true + - in: query + name: email_verification + schema: + type: boolean + required: false + requestBody: + description: User details (some fields are ignored during update) + content: + application/json: + schema: + $ref: '#/components/schemas/UserInfo' + responses: + 200: + $ref: '#/components/schemas/UserInfo' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /avatar/{id}: get: tags: @@ -1196,6 +1429,26 @@ paths: 404: $ref: '#/components/responses/NotFound' + /validateSubToken: + get: + tags: + - Security + summary: Allows any microservice to validate a token and get security policy for a specific user. + operationId: validateToken + parameters: + - in: query + name: token + schema: + type: string + required: true + responses: + 200: + $ref: '#/components/schemas/TokenValidationResult' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /system: post: tags: diff --git a/src/APIServers.cpp b/src/APIServers.cpp index 1a059e3..fa982e1 100644 --- a/src/APIServers.cpp +++ b/src/APIServers.cpp @@ -4,34 +4,40 @@ #include "framework/MicroService.h" -#include "RESTAPI/RESTAPI_oauth2Handler.h" +#include "RESTAPI/RESTAPI_oauth2_handler.h" #include "RESTAPI/RESTAPI_user_handler.h" #include "RESTAPI/RESTAPI_users_handler.h" #include "RESTAPI/RESTAPI_action_links.h" -#include "RESTAPI/RESTAPI_systemEndpoints_handler.h" -#include "RESTAPI/RESTAPI_AssetServer.h" -#include "RESTAPI/RESTAPI_avatarHandler.h" +#include "RESTAPI/RESTAPI_system_endpoints_handler.h" +#include "RESTAPI/RESTAPI_asset_server.h" +#include "RESTAPI/RESTAPI_avatar_handler.h" #include "RESTAPI/RESTAPI_email_handler.h" #include "RESTAPI/RESTAPI_sms_handler.h" -#include "RESTAPI/RESTAPI_validateToken_handler.h" +#include "RESTAPI/RESTAPI_validate_token_handler.h" #include "RESTAPI/RESTAPI_preferences.h" +#include "RESTAPI/RESTAPI_suboauth2_handler.h" +#include "RESTAPI/RESTAPI_subuser_handler.h" +#include "RESTAPI/RESTAPI_subusers_handler.h" namespace OpenWifi { Poco::Net::HTTPRequestHandler * RESTAPI_external_server(const char *Path, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & L, RESTAPI_GenericServer & S) { return RESTAPI_Router< - RESTAPI_oauth2Handler, + RESTAPI_oauth2_handler, RESTAPI_users_handler, RESTAPI_user_handler, RESTAPI_system_command, - RESTAPI_AssetServer, - RESTAPI_systemEndpoints_handler, + RESTAPI_asset_server, + RESTAPI_system_endpoints_handler, RESTAPI_action_links, - RESTAPI_avatarHandler, + RESTAPI_avatar_handler, RESTAPI_email_handler, RESTAPI_sms_handler, - RESTAPI_preferences + RESTAPI_preferences, + RESTAPI_suboauth2_handler, + RESTAPI_subuser_handler, + RESTAPI_subusers_handler >(Path, Bindings, L, S); } @@ -42,8 +48,11 @@ namespace OpenWifi { RESTAPI_user_handler, RESTAPI_system_command, RESTAPI_action_links, - RESTAPI_validateToken_handler, - RESTAPI_sms_handler + RESTAPI_validate_token_handler, + RESTAPI_sms_handler, + RESTAPI_suboauth2_handler, + RESTAPI_subuser_handler, + RESTAPI_subusers_handler >(Path, Bindings, L, S); } } \ No newline at end of file diff --git a/src/AuthService.cpp b/src/AuthService.cpp index 7d10175..8ca1e21 100644 --- a/src/AuthService.cpp +++ b/src/AuthService.cpp @@ -47,6 +47,7 @@ namespace OpenWifi { Signer_.addAllAlgorithms(); Logger_.notice("Starting..."); PasswordValidation_ = PasswordValidationStr_ = MicroService::instance().ConfigGetString("authentication.validation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$"); + SubPasswordValidation_ = SubPasswordValidationStr_ = MicroService::instance().ConfigGetString("authentication.subvalidation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$"); TokenAging_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60); HowManyOldPassword_ = MicroService::instance().ConfigGetInt("authentication.oldpasswords", 5); return 0; @@ -99,11 +100,59 @@ namespace OpenWifi { return false; } + bool AuthService::IsSubAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) + { + std::lock_guard Guard(Mutex_); + Expired = false; + try { + std::string CallToken; + Poco::Net::OAuth20Credentials Auth(Request); + if (Auth.getScheme() == "Bearer") { + CallToken = Auth.getBearerToken(); + } + + if(!CallToken.empty()) { + auto Client = SubUserCache_.get(CallToken); + if( Client.isNull() ) { + SecurityObjects::UserInfoAndPolicy UInfo2; + uint64_t RevocationDate=0; + if(StorageService()->GetSubToken(CallToken,UInfo2,RevocationDate)) { + if(RevocationDate!=0) + return false; + Expired = (UInfo2.webtoken.created_ + UInfo2.webtoken.expires_in_) < time(nullptr); + if(StorageService()->GetSubUserById(UInfo2.userinfo.Id,UInfo.userinfo)) { + UInfo.webtoken = UInfo2.webtoken; + SubUserCache_.update(CallToken, UInfo); + SessionToken = CallToken; + return true; + } + } + return false; + } + if(!Expired) { + SessionToken = CallToken; + UInfo = *Client ; + return true; + } + RevokeSubToken(CallToken); + return false; + } + } catch(const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + void AuthService::RevokeToken(std::string & Token) { UserCache_.remove(Token); StorageService()->RevokeToken(Token); } + void AuthService::RevokeSubToken(std::string & Token) { + UserCache_.remove(Token); + StorageService()->RevokeSubToken(Token); + } + bool AuthService::DeleteUserFromCache(const std::string &UserName) { std::lock_guard Guard(Mutex_); @@ -121,6 +170,23 @@ namespace OpenWifi { return true; } + bool AuthService::DeleteSubUserFromCache(const std::string &UserName) { + std::lock_guard Guard(Mutex_); + + std::vector OldTokens; + + SubUserCache_.forEach([&OldTokens,UserName](const std::string &token, const SecurityObjects::UserInfoAndPolicy& O) -> void + { if(O.userinfo.email==UserName) + OldTokens.push_back(token); + }); + + for(const auto &i:OldTokens) { + SubLogout(i,false); + SubUserCache_.remove(i); + } + return true; + } + bool AuthService::RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo) { return (UInfo.userinfo.userTypeProprietaryInfo.mfa.enabled && MFAServer().MethodEnabled(UInfo.userinfo.userTypeProprietaryInfo.mfa.method)); } @@ -129,6 +195,10 @@ namespace OpenWifi { return std::regex_match(Password, PasswordValidation_); } + bool AuthService::ValidateSubPassword(const std::string &Password) { + return std::regex_match(Password, SubPasswordValidation_); + } + void AuthService::Logout(const std::string &token, bool EraseFromCache) { std::lock_guard Guard(Mutex_); @@ -148,6 +218,25 @@ namespace OpenWifi { } } + void AuthService::SubLogout(const std::string &token, bool EraseFromCache) { + std::lock_guard Guard(Mutex_); + + try { + Poco::JSON::Object Obj; + Obj.set("event", "remove-token"); + Obj.set("id", MicroService::instance().ID()); + Obj.set("token", token); + std::stringstream ResultText; + Poco::JSON::Stringifier::stringify(Obj, ResultText); + std::string Tmp{token}; + RevokeSubToken(Tmp); + KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, MicroService::instance().PrivateEndPoint(), ResultText.str(), + false); + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + } + [[nodiscard]] std::string AuthService::GenerateTokenHMAC(const std::string & UserName, ACCESS_TYPE Type) { std::string Identity(UserName + ":" + Poco::format("%d",(int)std::time(nullptr)) + ":" + std::to_string(rand())); HMAC_.update(Identity); @@ -198,6 +287,30 @@ namespace OpenWifi { UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_); } + void AuthService::CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo) + { + std::lock_guard Guard(Mutex_); + + SecurityObjects::AclTemplate ACL; + ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true; + UInfo.webtoken.acl_template_ = ACL; + UInfo.webtoken.expires_in_ = TokenAging_ ; + UInfo.webtoken.idle_timeout_ = 5 * 60; + UInfo.webtoken.token_type_ = "Bearer"; + UInfo.webtoken.access_token_ = GenerateTokenHMAC(UInfo.userinfo.Id,USERNAME); + UInfo.webtoken.id_token_ = GenerateTokenHMAC(UInfo.userinfo.Id,USERNAME); + UInfo.webtoken.refresh_token_ = GenerateTokenHMAC(UInfo.userinfo.Id,CUSTOM); + UInfo.webtoken.created_ = time(nullptr); + UInfo.webtoken.username_ = UserName; + UInfo.webtoken.errorCode = 0; + UInfo.webtoken.userMustChangePassword = false; + SubUserCache_.update(UInfo.webtoken.access_token_,UInfo); + StorageService()->SetSubLastLogin(UInfo.userinfo.Id); + StorageService()->AddSubToken(UInfo.userinfo.Id, UInfo.webtoken.access_token_, + UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_, + UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_); + } + bool AuthService::SetPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { std::lock_guard G(Mutex_); @@ -232,6 +345,40 @@ namespace OpenWifi { return true; } + bool AuthService::SetSubPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { + 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()); + } + + 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()); @@ -261,6 +408,23 @@ namespace OpenWifi { return false; } + bool AuthService::ValidateSubPasswordHash(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; + } + UNAUTHORIZED_REASON AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired ) { std::lock_guard Guard(Mutex_); @@ -306,6 +470,51 @@ namespace OpenWifi { return INVALID_CREDENTIALS; } + UNAUTHORIZED_REASON AuthService::AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired ) + { + std::lock_guard Guard(Mutex_); + + Poco::toLowerInPlace(UserName); + + if(StorageService()->GetSubUserByEmail(UserName,UInfo.userinfo)) { + if(UInfo.userinfo.waitingForEmailCheck) { + return USERNAME_PENDING_VERIFICATION; + } + + if(!ValidateSubPasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) { + return INVALID_CREDENTIALS; + } + + if(UInfo.userinfo.changePassword && NewPassword.empty()) { + UInfo.webtoken.userMustChangePassword = true ; + return PASSWORD_CHANGE_REQUIRED; + } + + if(!NewPassword.empty() && !ValidateSubPassword(NewPassword)) { + return PASSWORD_INVALID; + } + + if(UInfo.userinfo.changePassword || !NewPassword.empty()) { + if(!SetSubPassword(NewPassword,UInfo.userinfo)) { + UInfo.webtoken.errorCode = 1; + return PASSWORD_ALREADY_USED; + } + UInfo.userinfo.lastPasswordChange = std::time(nullptr); + UInfo.userinfo.changePassword = false; + StorageService()->UpdateSubUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.Id,UInfo.userinfo); + } + + // so we have a good password, password up date has taken place if need be, now generate the token. + UInfo.userinfo.lastLogin=std::time(nullptr); + StorageService()->SetSubLastLogin(UInfo.userinfo.Id); + CreateSubToken(UserName, UInfo ); + + return SUCCESS; + } + + return INVALID_CREDENTIALS; + } + bool AuthService::SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { SecurityObjects::UserInfo UInfo; @@ -341,6 +550,41 @@ namespace OpenWifi { return false; } + bool AuthService::SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { + SecurityObjects::UserInfo UInfo; + + if(StorageService()->GetSubUserByEmail(Email,UInfo)) { + switch (Reason) { + + case FORGOT_PASSWORD: { + MessageAttributes Attrs; + Attrs[RECIPIENT_EMAIL] = UInfo.email; + Attrs[LOGO] = GetLogoAssetURI(); + Attrs[SUBJECT] = "Password reset link"; + Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ; + SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs); + } + break; + + case EMAIL_VERIFICATION: { + MessageAttributes Attrs; + Attrs[RECIPIENT_EMAIL] = UInfo.email; + Attrs[LOGO] = GetLogoAssetURI(); + Attrs[SUBJECT] = "EMail Address Verification"; + Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ; + SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); + UInfo.waitingForEmailCheck = true; + } + break; + + default: + break; + } + return true; + } + return false; + } + bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) { SecurityObjects::ActionLink A; @@ -354,6 +598,19 @@ namespace OpenWifi { return true; } + bool AuthService::VerifySubEmail(SecurityObjects::UserInfo &UInfo) { + SecurityObjects::ActionLink A; + + A.action = OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL; + A.userId = UInfo.email; + A.id = MicroService::CreateUUID(); + A.created = std::time(nullptr); + A.expires = A.created + 24*60*60; + StorageService()->CreateAction(A); + UInfo.waitingForEmailCheck = true; + return true; + } + bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) { std::lock_guard G(Mutex_); @@ -388,5 +645,38 @@ namespace OpenWifi { return false; } + bool AuthService::IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) { + std::lock_guard G(Mutex_); + + Expired = false; + + auto Client = SubUserCache_.get(Token); + if(!Client.isNull()) { + Expired = (Client->webtoken.created_ + Client->webtoken.expires_in_) < std::time(nullptr); + WebToken = Client->webtoken; + UserInfo = Client->userinfo; + return true; + } + + std::string TToken{Token}; + if(StorageService()->IsSubTokenRevoked(TToken)) { + return false; + } + + // get the token from disk... + SecurityObjects::UserInfoAndPolicy UInfo; + uint64_t RevocationDate=0; + if(StorageService()->GetSubToken(TToken, UInfo, RevocationDate)) { + if(RevocationDate!=0) + return false; + Expired = (UInfo.webtoken.created_ + UInfo.webtoken.expires_in_) < std::time(nullptr); + if(StorageService()->GetSubUserById(UInfo.userinfo.Id,UInfo.userinfo)) { + WebToken = UInfo.webtoken; + SubUserCache_.update(UInfo.webtoken.access_token_, UInfo); + return true; + } + } + return false; + } } // end of namespace diff --git a/src/AuthService.h b/src/AuthService.h index a7eb7b3..6db7062 100644 --- a/src/AuthService.h +++ b/src/AuthService.h @@ -59,23 +59,42 @@ namespace OpenWifi{ [[nodiscard]] const std:: string & PasswordValidationExpression() const { return PasswordValidationStr_;}; void Logout(const std::string &token, bool EraseFromCache=true); + [[nodiscard]] bool IsSubAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired); + [[nodiscard]] UNAUTHORIZED_REASON AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ); + void CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo); + [[nodiscard]] bool SetSubPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo); + [[nodiscard]] const std:: string & SubPasswordValidationExpression() const { return PasswordValidationStr_;}; + void SubLogout(const std::string &token, bool EraseFromCache=true); + bool ValidatePassword(const std::string &pwd); + bool ValidateSubPassword(const std::string &pwd); [[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired); + [[nodiscard]] bool IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired); [[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 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 ValidateSubPasswordHash(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]] bool UpdateSubPassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); + [[nodiscard]] std::string ResetSubPassword(const std::string &Admin, const std::string &UserName); + [[nodiscard]] static bool VerifyEmail(SecurityObjects::UserInfo &UInfo); + [[nodiscard]] static bool VerifySubEmail(SecurityObjects::UserInfo &UInfo); + [[nodiscard]] static bool SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason); + [[nodiscard]] static bool SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason); [[nodiscard]] bool DeleteUserFromCache(const std::string &UserName); + [[nodiscard]] bool DeleteSubUserFromCache(const std::string &UserName); [[nodiscard]] bool RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo); + void RevokeToken(std::string & Token); + void RevokeSubToken(std::string & Token); [[nodiscard]] static inline const std::string GetLogoAssetURI() { return MicroService::instance().PublicEndPoint() + "/wwwassets/the_logo.png"; @@ -88,11 +107,16 @@ namespace OpenWifi{ private: Poco::JWT::Signer Signer_; Poco::SHA2Engine SHA2_; - Poco::ExpireLRUCache UserCache_{2048,1200000}; - // SecurityObjects::UserInfoCache UserCache_; - std::string PasswordValidationStr_; - std::regex PasswordValidation_; - uint64_t TokenAging_ = 30 * 24 * 60 * 60; + + Poco::ExpireLRUCache UserCache_{256,1200000}; + Poco::ExpireLRUCache SubUserCache_{4096,1200000}; + + std::string PasswordValidationStr_; + std::string SubPasswordValidationStr_; + std::regex PasswordValidation_; + std::regex SubPasswordValidation_; + + uint64_t TokenAging_ = 30 * 24 * 60 * 60; uint64_t HowManyOldPassword_=5; class SHA256Engine : public Poco::Crypto::DigestEngine @@ -121,8 +145,11 @@ namespace OpenWifi{ inline AuthService * AuthService() { return AuthService::instance(); } - [[nodiscard]] inline bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired) { - return AuthService()->IsAuthorized(Request, SessionToken, UInfo, Expired ); + [[nodiscard]] inline bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired, bool Sub ) { + if(Sub) + return AuthService()->IsSubAuthorized(Request, SessionToken, UInfo, Expired ); + else + return AuthService()->IsAuthorized(Request, SessionToken, UInfo, Expired ); } } // end of namespace diff --git a/src/RESTAPI/RESTAPI_action_links.h b/src/RESTAPI/RESTAPI_action_links.h index ce008d2..24330c6 100644 --- a/src/RESTAPI/RESTAPI_action_links.h +++ b/src/RESTAPI/RESTAPI_action_links.h @@ -2,9 +2,7 @@ // Created by stephane bourque on 2021-06-22. // -#ifndef UCENTRALSEC_RESTAPI_ACTION_LINKS_H -#define UCENTRALSEC_RESTAPI_ACTION_LINKS_H - +#pragma once #include "framework/MicroService.h" @@ -33,5 +31,3 @@ namespace OpenWifi { void DoPut() final {}; }; } - -#endif //UCENTRALSEC_RESTAPI_ACTION_LINKS_H diff --git a/src/RESTAPI/RESTAPI_AssetServer.cpp b/src/RESTAPI/RESTAPI_asset_server.cpp similarity index 88% rename from src/RESTAPI/RESTAPI_AssetServer.cpp rename to src/RESTAPI/RESTAPI_asset_server.cpp index 5b36626..a7a8318 100644 --- a/src/RESTAPI/RESTAPI_AssetServer.cpp +++ b/src/RESTAPI/RESTAPI_asset_server.cpp @@ -2,13 +2,13 @@ // Created by stephane bourque on 2021-07-10. // -#include "RESTAPI_AssetServer.h" +#include "RESTAPI_asset_server.h" #include "Poco/File.h" #include "framework/RESTAPI_protocol.h" #include "Daemon.h" namespace OpenWifi { - void RESTAPI_AssetServer::DoGet() { + void RESTAPI_asset_server::DoGet() { Poco::File AssetFile; if(Request->getURI().find("/favicon.ico") != std::string::npos) { diff --git a/src/RESTAPI/RESTAPI_AssetServer.h b/src/RESTAPI/RESTAPI_asset_server.h similarity index 77% rename from src/RESTAPI/RESTAPI_AssetServer.h rename to src/RESTAPI/RESTAPI_asset_server.h index fc05ad7..77b65e5 100644 --- a/src/RESTAPI/RESTAPI_AssetServer.h +++ b/src/RESTAPI/RESTAPI_asset_server.h @@ -2,15 +2,14 @@ // Created by stephane bourque on 2021-07-10. // -#ifndef UCENTRALSEC_RESTAPI_ASSETSERVER_H -#define UCENTRALSEC_RESTAPI_ASSETSERVER_H +#pragma once #include "../framework/MicroService.h" namespace OpenWifi { - class RESTAPI_AssetServer : public RESTAPIHandler { + class RESTAPI_asset_server : public RESTAPIHandler { public: - RESTAPI_AssetServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + RESTAPI_asset_server(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) : RESTAPIHandler(bindings, L, std::vector {Poco::Net::HTTPRequest::HTTP_POST, @@ -32,5 +31,3 @@ namespace OpenWifi { }; } - -#endif //UCENTRALSEC_RESTAPI_ASSETSERVER_H diff --git a/src/RESTAPI/RESTAPI_avatarHandler.cpp b/src/RESTAPI/RESTAPI_avatar_handler.cpp similarity index 94% rename from src/RESTAPI/RESTAPI_avatarHandler.cpp rename to src/RESTAPI/RESTAPI_avatar_handler.cpp index e66112d..aab98b6 100644 --- a/src/RESTAPI/RESTAPI_avatarHandler.cpp +++ b/src/RESTAPI/RESTAPI_avatar_handler.cpp @@ -5,7 +5,7 @@ #include #include -#include "RESTAPI_avatarHandler.h" +#include "RESTAPI_avatar_handler.h" #include "StorageService.h" #include "Poco/Net/HTMLForm.h" #include "framework/RESTAPI_protocol.h" @@ -27,7 +27,7 @@ namespace OpenWifi { Length_ = InputStream.chars(); }; - void RESTAPI_avatarHandler::DoPost() { + void RESTAPI_avatar_handler::DoPost() { std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); SecurityObjects::UserInfo UInfo; @@ -57,7 +57,7 @@ namespace OpenWifi { ReturnObject(Answer); } - void RESTAPI_avatarHandler::DoGet() { + void RESTAPI_avatar_handler::DoGet() { std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); if (Id.empty()) { return NotFound(); @@ -70,7 +70,7 @@ namespace OpenWifi { SendFile(TempAvatar, Type, Name); } - void RESTAPI_avatarHandler::DoDelete() { + void RESTAPI_avatar_handler::DoDelete() { std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); if (Id.empty()) { return NotFound(); diff --git a/src/RESTAPI/RESTAPI_avatarHandler.h b/src/RESTAPI/RESTAPI_avatar_handler.h similarity index 84% rename from src/RESTAPI/RESTAPI_avatarHandler.h rename to src/RESTAPI/RESTAPI_avatar_handler.h index 18d9c67..fa6282e 100644 --- a/src/RESTAPI/RESTAPI_avatarHandler.h +++ b/src/RESTAPI/RESTAPI_avatar_handler.h @@ -1,10 +1,7 @@ // // Created by stephane bourque on 2021-07-15. // - -#ifndef UCENTRALSEC_RESTAPI_AVATARHANDLER_H -#define UCENTRALSEC_RESTAPI_AVATARHANDLER_H - +#pragma once #include "framework/MicroService.h" @@ -31,9 +28,9 @@ namespace OpenWifi { Poco::TemporaryFile &TempFile_; }; - class RESTAPI_avatarHandler : public RESTAPIHandler { + class RESTAPI_avatar_handler : public RESTAPIHandler { public: - RESTAPI_avatarHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + RESTAPI_avatar_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) : RESTAPIHandler(bindings, L, std::vector{ Poco::Net::HTTPRequest::HTTP_GET, @@ -51,4 +48,3 @@ namespace OpenWifi { }; } -#endif //UCENTRALSEC_RESTAPI_AVATARHANDLER_H diff --git a/src/RESTAPI/RESTAPI_email_handler.h b/src/RESTAPI/RESTAPI_email_handler.h index 8ec3139..08e2fe1 100644 --- a/src/RESTAPI/RESTAPI_email_handler.h +++ b/src/RESTAPI/RESTAPI_email_handler.h @@ -2,9 +2,7 @@ // Created by stephane bourque on 2021-09-02. // -#ifndef OWSEC_RESTAPI_EMAIL_HANDLER_H -#define OWSEC_RESTAPI_EMAIL_HANDLER_H - +#pragma once #include "framework/MicroService.h" @@ -24,5 +22,3 @@ namespace OpenWifi { void DoPut() final {}; }; } - -#endif //OWSEC_RESTAPI_EMAIL_HANDLER_H diff --git a/src/RESTAPI/RESTAPI_oauth2Handler.cpp b/src/RESTAPI/RESTAPI_oauth2_handler.cpp similarity index 97% rename from src/RESTAPI/RESTAPI_oauth2Handler.cpp rename to src/RESTAPI/RESTAPI_oauth2_handler.cpp index 709ec25..cd823c2 100644 --- a/src/RESTAPI/RESTAPI_oauth2Handler.cpp +++ b/src/RESTAPI/RESTAPI_oauth2_handler.cpp @@ -10,7 +10,7 @@ #include "Daemon.h" #include "AuthService.h" -#include "RESTAPI_oauth2Handler.h" +#include "RESTAPI_oauth2_handler.h" #include "MFAServer.h" #include "framework/RESTAPI_protocol.h" #include "framework/MicroService.h" @@ -24,7 +24,7 @@ namespace OpenWifi { U.oauthType.clear(); } - void RESTAPI_oauth2Handler::DoGet() { + void RESTAPI_oauth2_handler::DoGet() { bool Expired = false; if (!IsAuthorized(Expired)) { if(Expired) @@ -43,7 +43,7 @@ namespace OpenWifi { BadRequest(RESTAPI::Errors::UnrecognizedRequest); } - void RESTAPI_oauth2Handler::DoDelete() { + void RESTAPI_oauth2_handler::DoDelete() { bool Expired = false; if (!IsAuthorized(Expired)) { if(Expired) @@ -61,7 +61,7 @@ namespace OpenWifi { NotFound(); } - void RESTAPI_oauth2Handler::DoPost() { + void RESTAPI_oauth2_handler::DoPost() { auto Obj = ParseStream(); auto userId = GetS(RESTAPI::Protocol::USERID, Obj); auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); diff --git a/src/RESTAPI/RESTAPI_oauth2Handler.h b/src/RESTAPI/RESTAPI_oauth2_handler.h similarity index 74% rename from src/RESTAPI/RESTAPI_oauth2Handler.h rename to src/RESTAPI/RESTAPI_oauth2_handler.h index ea19b22..205c8db 100644 --- a/src/RESTAPI/RESTAPI_oauth2Handler.h +++ b/src/RESTAPI/RESTAPI_oauth2_handler.h @@ -6,15 +6,13 @@ // Arilia Wireless Inc. // -#ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H -#define UCENTRAL_RESTAPI_OAUTH2HANDLER_H - +#pragma once #include "framework/MicroService.h" namespace OpenWifi { - class RESTAPI_oauth2Handler : public RESTAPIHandler { + class RESTAPI_oauth2_handler : public RESTAPIHandler { public: - RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + RESTAPI_oauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) : RESTAPIHandler(bindings, L, std::vector{Poco::Net::HTTPRequest::HTTP_POST, Poco::Net::HTTPRequest::HTTP_DELETE, @@ -29,4 +27,5 @@ namespace OpenWifi { void DoPut() final {}; }; } -#endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H + + diff --git a/src/RESTAPI/RESTAPI_preferences.h b/src/RESTAPI/RESTAPI_preferences.h index cd67a99..03c11b4 100644 --- a/src/RESTAPI/RESTAPI_preferences.h +++ b/src/RESTAPI/RESTAPI_preferences.h @@ -2,8 +2,7 @@ // Created by stephane bourque on 2021-11-16. // -#ifndef OWSEC_RESTAPI_PREFERENCES_H -#define OWSEC_RESTAPI_PREFERENCES_H +#pragma once #include "framework/MicroService.h" @@ -25,5 +24,3 @@ namespace OpenWifi { void DoDelete() final {}; }; } - -#endif //OWSEC_RESTAPI_PREFERENCES_H diff --git a/src/RESTAPI/RESTAPI_sms_handler.h b/src/RESTAPI/RESTAPI_sms_handler.h index 163c21a..352f8c2 100644 --- a/src/RESTAPI/RESTAPI_sms_handler.h +++ b/src/RESTAPI/RESTAPI_sms_handler.h @@ -2,9 +2,7 @@ // Created by stephane bourque on 2021-10-09. // -#ifndef OWSEC_RESTAPI_SMS_HANDLER_H -#define OWSEC_RESTAPI_SMS_HANDLER_H - +#pragma once #include "framework/MicroService.h" @@ -24,5 +22,3 @@ namespace OpenWifi { void DoPut() final {}; }; } - -#endif //OWSEC_RESTAPI_SMS_HANDLER_H diff --git a/src/RESTAPI/RESTAPI_suboauth2_handler.cpp b/src/RESTAPI/RESTAPI_suboauth2_handler.cpp new file mode 100644 index 0000000..8c048d8 --- /dev/null +++ b/src/RESTAPI/RESTAPI_suboauth2_handler.cpp @@ -0,0 +1,157 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "RESTAPI_suboauth2_handler.h" +#include "Daemon.h" +#include "AuthService.h" +#include "MFAServer.h" +#include "framework/RESTAPI_protocol.h" +#include "framework/MicroService.h" +#include "StorageService.h" + +namespace OpenWifi { + + static void FilterCredentials(SecurityObjects::UserInfo & U) { + U.currentPassword.clear(); + U.lastPasswords.clear(); + U.oauthType.clear(); + } + + void RESTAPI_suboauth2_handler::DoGet() { + bool Expired = false; + if (!IsAuthorized(Expired, true)) { + if(Expired) + return UnAuthorized(RESTAPI::Errors::ExpiredToken,EXPIRED_TOKEN); + return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation); + } + bool GetMe = GetBoolParameter(RESTAPI::Protocol::ME, false); + if(GetMe) { + Logger_.information(Poco::format("REQUEST-ME(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); + Poco::JSON::Object Me; + SecurityObjects::UserInfo ReturnedUser = UserInfo_.userinfo; + FilterCredentials(ReturnedUser); + ReturnedUser.to_json(Me); + return ReturnObject(Me); + } + BadRequest(RESTAPI::Errors::UnrecognizedRequest); + } + + void RESTAPI_suboauth2_handler::DoDelete() { + bool Expired = false; + if (!IsAuthorized(Expired, true)) { + if(Expired) + return UnAuthorized(RESTAPI::Errors::ExpiredToken,EXPIRED_TOKEN); + return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation); + } + + auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "..."); + if (Token == SessionToken_) { + AuthService()->Logout(Token); + return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); + } + + Logger_.information(Poco::format("BAD-LOGOUT(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); + NotFound(); + } + + void RESTAPI_suboauth2_handler::DoPost() { + auto Obj = ParseStream(); + auto userId = GetS(RESTAPI::Protocol::USERID, Obj); + auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); + auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj); + + Poco::toLowerInPlace(userId); + + if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS, false)) { + Logger_.information(Poco::format("POLICY-REQUEST(%s): Request.", Request->clientAddress().toString())); + Poco::JSON::Object Answer; + Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->SubPasswordValidationExpression()); + Answer.set(RESTAPI::Protocol::ACCESSPOLICY, Daemon()->GetAccessPolicy()); + Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, Daemon()->GetPasswordPolicy()); + return ReturnObject(Answer); + } + + if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) { + SecurityObjects::UserInfo UInfo1; + auto UserExists = StorageService()->GetSubUserByEmail(userId,UInfo1); + if(UserExists) { + Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", Request->clientAddress().toString(), userId)); + SecurityObjects::ActionLink NewLink; + + NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD; + NewLink.id = MicroService::CreateUUID(); + NewLink.userId = UInfo1.Id; + NewLink.created = std::time(nullptr); + NewLink.expires = NewLink.created + (24*60*60); + StorageService()->CreateAction(NewLink); + + Poco::JSON::Object ReturnObj; + SecurityObjects::UserInfoAndPolicy UInfo; + UInfo.webtoken.userMustChangePassword = true; + UInfo.webtoken.to_json(ReturnObj); + return ReturnObject(ReturnObj); + } else { + Poco::JSON::Object ReturnObj; + SecurityObjects::UserInfoAndPolicy UInfo; + UInfo.webtoken.userMustChangePassword = true; + UInfo.webtoken.to_json(ReturnObj); + return ReturnObject(ReturnObj); + } + } + + if(GetBoolParameter(RESTAPI::Protocol::RESENDMFACODE,false)) { + Logger_.information(Poco::format("RESEND-MFA-CODE(%s): Request for %s", Request->clientAddress().toString(), userId)); + if(Obj->has("uuid")) { + auto uuid = Obj->get("uuid").toString(); + if(MFAServer().ResendCode(uuid)) + return OK(); + } + return UnAuthorized(RESTAPI::Errors::InvalidCredentials); + } + + if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE,false)) { + Logger_.information(Poco::format("COMPLETE-MFA-CHALLENGE(%s): Request for %s", Request->clientAddress().toString(), userId)); + if(Obj->has("uuid")) { + SecurityObjects::UserInfoAndPolicy UInfo; + if(MFAServer().CompleteMFAChallenge(Obj,UInfo)) { + Poco::JSON::Object ReturnObj; + UInfo.webtoken.to_json(ReturnObj); + return ReturnObject(ReturnObj); + } + } + return UnAuthorized(RESTAPI::Errors::InvalidCredentials); + } + + SecurityObjects::UserInfoAndPolicy UInfo; + bool Expired=false; + auto Code=AuthService()->AuthorizeSub(userId, password, newPassword, UInfo, Expired); + if (Code==SUCCESS) { + Poco::JSON::Object ReturnObj; + if(AuthService()->RequiresMFA(UInfo)) { + if(MFAServer().StartMFAChallenge(UInfo, ReturnObj)) { + return ReturnObject(ReturnObj); + } + Logger_.warning("MFA Seems to be broken. Please fix. Disabling MFA checking for now."); + } + UInfo.webtoken.to_json(ReturnObj); + return ReturnObject(ReturnObj); + } else { + switch(Code) { + case INVALID_CREDENTIALS: + return UnAuthorized(RESTAPI::Errors::InvalidCredentials, Code); + case PASSWORD_INVALID: + return UnAuthorized(RESTAPI::Errors::InvalidPassword, Code); + case PASSWORD_ALREADY_USED: + return UnAuthorized(RESTAPI::Errors::PasswordRejected, Code); + case USERNAME_PENDING_VERIFICATION: + return UnAuthorized(RESTAPI::Errors::UserPendingVerification, Code); + case PASSWORD_CHANGE_REQUIRED: + return UnAuthorized(RESTAPI::Errors::PasswordMustBeChanged, Code); + default: + return UnAuthorized(RESTAPI::Errors::InvalidCredentials); break; + } + return; + } + } +} \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_suboauth2_handler.h b/src/RESTAPI/RESTAPI_suboauth2_handler.h new file mode 100644 index 0000000..ea8729c --- /dev/null +++ b/src/RESTAPI/RESTAPI_suboauth2_handler.h @@ -0,0 +1,26 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once +#include "framework/MicroService.h" + +namespace OpenWifi { + class RESTAPI_suboauth2_handler : public RESTAPIHandler { + public: + RESTAPI_suboauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector{Poco::Net::HTTPRequest::HTTP_POST, + Poco::Net::HTTPRequest::HTTP_DELETE, + Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, + Internal, false, false , RateLimit{.Interval=1000,.MaxCalls=10}, + false) {} + static const std::list PathName() { return std::list{"/api/v1/suboauth2/{token}","/api/v1/suboauth2"}; }; + void DoGet() final; + void DoPost() final; + void DoDelete() final; + void DoPut() final {}; + }; +} diff --git a/src/RESTAPI/RESTAPI_subuser_handler.cpp b/src/RESTAPI/RESTAPI_subuser_handler.cpp new file mode 100644 index 0000000..8d54514 --- /dev/null +++ b/src/RESTAPI/RESTAPI_subuser_handler.cpp @@ -0,0 +1,239 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "RESTAPI_subuser_handler.h" +#include "StorageService.h" +#include "Poco/JSON/Parser.h" +#include "framework/RESTAPI_errors.h" +#include "SMSSender.h" +#include "ACLProcessor.h" + +namespace OpenWifi { + + static void FilterCredentials(SecurityObjects::UserInfo & U) { + U.currentPassword.clear(); + U.lastPasswords.clear(); + U.oauthType.clear(); + } + + void RESTAPI_subuser_handler::DoGet() { + std::string Id = GetBinding("id", ""); + if(Id.empty()) { + return BadRequest(RESTAPI::Errors::MissingUserID); + } + + Poco::toLowerInPlace(Id); + std::string Arg; + SecurityObjects::UserInfo UInfo; + if(HasParameter("byEmail",Arg) && Arg=="true") { + if(!StorageService()->GetSubUserByEmail(Id,UInfo)) { + return NotFound(); + } + } else if(!StorageService()->GetSubUserById(Id,UInfo)) { + return NotFound(); + } + + Poco::JSON::Object UserInfoObject; + FilterCredentials(UInfo); + UInfo.to_json(UserInfoObject); + ReturnObject(UserInfoObject); + } + + void RESTAPI_subuser_handler::DoDelete() { + std::string Id = GetBinding("id", ""); + if(Id.empty()) { + return BadRequest(RESTAPI::Errors::MissingUserID); + } + + SecurityObjects::UserInfo UInfo; + if(!StorageService()->GetUserById(Id,UInfo)) { + return NotFound(); + } + + if(!ACLProcessor::Can(UserInfo_.userinfo, UInfo,ACLProcessor::DELETE)) { + return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, ACCESS_DENIED); + } + + if(!StorageService()->DeleteSubUser(UserInfo_.userinfo.email,Id)) { + return NotFound(); + } + + if(AuthService()->DeleteSubUserFromCache(UInfo.email)) { + // nothing to do + } + + Logger_.information(Poco::format("Remove all tokens for '%s'", UserInfo_.userinfo.email)); + StorageService()->RevokeAllSubTokens(UInfo.email); + Logger_.information(Poco::format("User '%s' deleted by '%s'.",Id,UserInfo_.userinfo.email)); + OK(); + } + + void RESTAPI_subuser_handler::DoPost() { + std::string Id = GetBinding("id", ""); + if(Id!="0") { + return BadRequest(RESTAPI::Errors::IdMustBe0); + } + + SecurityObjects::UserInfo NewUser; + RESTAPI_utils::from_request(NewUser,*Request); + + if(NewUser.userRole == SecurityObjects::UNKNOWN) { + return BadRequest(RESTAPI::Errors::InvalidUserRole); + } + + if(!ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) { + return UnAuthorized("Insufficient access rights.", ACCESS_DENIED); + } + + Poco::toLowerInPlace(NewUser.email); + if(!Utils::ValidEMailAddress(NewUser.email)) { + return BadRequest(RESTAPI::Errors::InvalidEmailAddress); + } + + if(!NewUser.currentPassword.empty()) { + if(!AuthService()->ValidateSubPassword(NewUser.currentPassword)) { + return BadRequest(RESTAPI::Errors::InvalidPassword); + } + } + + if(NewUser.name.empty()) + NewUser.name = NewUser.email; + + if(!StorageService()->CreateSubUser(NewUser.email,NewUser)) { + Logger_.information(Poco::format("Could not add user '%s'.",NewUser.email)); + return BadRequest(RESTAPI::Errors::RecordNotCreated); + } + + if(GetParameter("email_verification","false")=="true") { + if(AuthService::VerifySubEmail(NewUser)) + Logger_.information(Poco::format("Verification e-mail requested for %s",NewUser.email)); + StorageService()->UpdateSubUserInfo(UserInfo_.userinfo.email,NewUser.Id,NewUser); + } + + if(!StorageService()->GetSubUserByEmail(NewUser.email, NewUser)) { + Logger_.information(Poco::format("User '%s' but not retrieved.",NewUser.email)); + return NotFound(); + } + + Poco::JSON::Object UserInfoObject; + FilterCredentials(NewUser); + NewUser.to_json(UserInfoObject); + ReturnObject(UserInfoObject); + Logger_.information(Poco::format("User '%s' has been added by '%s')",NewUser.email, UserInfo_.userinfo.email)); + } + + void RESTAPI_subuser_handler::DoPut() { + std::string Id = GetBinding("id", ""); + if(Id.empty()) { + return BadRequest(RESTAPI::Errors::MissingUserID); + } + + SecurityObjects::UserInfo Existing; + if(!StorageService()->GetSubUserById(Id,Existing)) { + return NotFound(); + } + + if(!ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) { + return UnAuthorized("Insufficient access rights.", ACCESS_DENIED); + } + + SecurityObjects::UserInfo NewUser; + auto RawObject = ParseStream(); + if(!NewUser.from_json(RawObject)) { + return BadRequest(RESTAPI::Errors::InvalidJSONDocument); + } + + // some basic validations + if(RawObject->has("userRole") && SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN) { + return BadRequest(RESTAPI::Errors::InvalidUserRole); + } + + // The only valid things to change are: changePassword, name, + AssignIfPresent(RawObject,"name", Existing.name); + AssignIfPresent(RawObject,"description", Existing.description); + AssignIfPresent(RawObject,"owner", Existing.owner); + AssignIfPresent(RawObject,"location", Existing.location); + AssignIfPresent(RawObject,"locale", Existing.locale); + AssignIfPresent(RawObject,"changePassword", Existing.changePassword); + AssignIfPresent(RawObject,"suspended", Existing.suspended); + AssignIfPresent(RawObject,"blackListed", Existing.blackListed); + + if(RawObject->has("userRole")) { + auto NewRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString()); + if(NewRole!=Existing.userRole) { + if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && NewRole==SecurityObjects::ROOT) { + return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, ACCESS_DENIED); + } + if(Id==UserInfo_.userinfo.Id) { + return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, ACCESS_DENIED); + } + Existing.userRole = NewRole; + } + } + + if(RawObject->has("notes")) { + SecurityObjects::NoteInfoVec NIV; + NIV = RESTAPI_utils::to_object_array(RawObject->get("notes").toString()); + for(auto const &i:NIV) { + SecurityObjects::NoteInfo ii{.created=(uint64_t)std::time(nullptr), .createdBy=UserInfo_.userinfo.email, .note=i.note}; + Existing.notes.push_back(ii); + } + } + if(RawObject->has("currentPassword")) { + if(!AuthService()->ValidateSubPassword(RawObject->get("currentPassword").toString())) { + return BadRequest(RESTAPI::Errors::InvalidPassword); + } + if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),Existing)) { + return BadRequest(RESTAPI::Errors::PasswordRejected); + } + } + + if(GetParameter("email_verification","false")=="true") { + if(AuthService::VerifySubEmail(Existing)) + Logger_.information(Poco::format("Verification e-mail requested for %s",Existing.email)); + } + + if(RawObject->has("userTypeProprietaryInfo")) { + bool ChangingMFA = NewUser.userTypeProprietaryInfo.mfa.enabled && !Existing.userTypeProprietaryInfo.mfa.enabled; + + Existing.userTypeProprietaryInfo.mfa.enabled = NewUser.userTypeProprietaryInfo.mfa.enabled; + + auto PropInfo = RawObject->get("userTypeProprietaryInfo"); + auto PInfo = PropInfo.extract(); + + if(PInfo->isArray("mobiles")) { + Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles; + } + + if(ChangingMFA && !NewUser.userTypeProprietaryInfo.mobiles.empty() && !SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number,UserInfo_.userinfo.email)){ + return BadRequest(RESTAPI::Errors::NeedMobileNumber); + } + + if(NewUser.userTypeProprietaryInfo.mfa.method=="sms" && Existing.userTypeProprietaryInfo.mobiles.empty()) { + return BadRequest(RESTAPI::Errors::NeedMobileNumber); + } + + if(!NewUser.userTypeProprietaryInfo.mfa.method.empty()) { + if(NewUser.userTypeProprietaryInfo.mfa.method!="email" && NewUser.userTypeProprietaryInfo.mfa.method!="sms" ) { + return BadRequest("Unknown MFA method"); + } + Existing.userTypeProprietaryInfo.mfa.method=NewUser.userTypeProprietaryInfo.mfa.method; + } + + if(Existing.userTypeProprietaryInfo.mfa.enabled && Existing.userTypeProprietaryInfo.mfa.method.empty()) { + return BadRequest("Illegal MFA method"); + } + } + + if(StorageService()->UpdateSubUserInfo(UserInfo_.userinfo.email,Id,Existing)) { + SecurityObjects::UserInfo NewUserInfo; + StorageService()->GetSubUserByEmail(UserInfo_.userinfo.email,NewUserInfo); + Poco::JSON::Object ModifiedObject; + FilterCredentials(NewUserInfo); + NewUserInfo.to_json(ModifiedObject); + return ReturnObject(ModifiedObject); + } + BadRequest(RESTAPI::Errors::RecordNotUpdated); + } +} \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_subuser_handler.h b/src/RESTAPI/RESTAPI_subuser_handler.h new file mode 100644 index 0000000..50507f8 --- /dev/null +++ b/src/RESTAPI/RESTAPI_subuser_handler.h @@ -0,0 +1,30 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include "framework/MicroService.h" + +namespace OpenWifi { + class RESTAPI_subuser_handler : public RESTAPIHandler { + public: + RESTAPI_subuser_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector + {Poco::Net::HTTPRequest::HTTP_POST, + Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_PUT, + Poco::Net::HTTPRequest::HTTP_DELETE, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, + Internal) {} + static const std::list PathName() { return std::list{"/api/v1/subuser/{id}"}; }; + void DoGet() final; + void DoPost() final; + void DoDelete() final; + void DoPut() final; + private: + + }; +} diff --git a/src/RESTAPI/RESTAPI_subusers_handler.cpp b/src/RESTAPI/RESTAPI_subusers_handler.cpp new file mode 100644 index 0000000..a678c3d --- /dev/null +++ b/src/RESTAPI/RESTAPI_subusers_handler.cpp @@ -0,0 +1,58 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "RESTAPI_subusers_handler.h" +#include "StorageService.h" +#include "framework/RESTAPI_protocol.h" +#include "framework/MicroService.h" + +namespace OpenWifi { + + void RESTAPI_subusers_handler::DoGet() { + std::vector Users; + bool IdOnly = (GetParameter("idOnly","false")=="true"); + + if(QB_.Select.empty()) { + Poco::JSON::Array ArrayObj; + Poco::JSON::Object Answer; + if (StorageService()->GetSubUsers(QB_.Offset, QB_.Limit, 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); + } + } + Answer.set(RESTAPI::Protocol::USERS, ArrayObj); + } + return ReturnObject(Answer); + } else { + Types::StringVec IDs = Utils::Split(QB_.Select); + Poco::JSON::Array ArrayObj; + for(auto &i:IDs) { + SecurityObjects::UserInfo UInfo; + if(StorageService()->GetSubUserById(i,UInfo)) { + Poco::JSON::Object Obj; + if (IdOnly) { + ArrayObj.add(UInfo.Id); + } else { + UInfo.currentPassword.clear(); + UInfo.lastPasswords.clear(); + UInfo.oauthType.clear(); + UInfo.to_json(Obj); + ArrayObj.add(Obj); + } + } + } + Poco::JSON::Object RetObj; + RetObj.set(RESTAPI::Protocol::USERS, ArrayObj); + return ReturnObject(RetObj); + } + } +} \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_subusers_handler.h b/src/RESTAPI/RESTAPI_subusers_handler.h new file mode 100644 index 0000000..594a518 --- /dev/null +++ b/src/RESTAPI/RESTAPI_subusers_handler.h @@ -0,0 +1,25 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include "framework/MicroService.h" + +namespace OpenWifi { + class RESTAPI_subusers_handler : public RESTAPIHandler { + public: + RESTAPI_subusers_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector + {Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, + Internal) {} + static const std::list PathName() { return std::list{"/api/v1/subusers"}; }; + void DoGet() final; + void DoPost() final {}; + void DoDelete() final {}; + void DoPut() final {}; + }; +}; diff --git a/src/RESTAPI/RESTAPI_systemEndpoints_handler.cpp b/src/RESTAPI/RESTAPI_system_endpoints_handler.cpp similarity index 85% rename from src/RESTAPI/RESTAPI_systemEndpoints_handler.cpp rename to src/RESTAPI/RESTAPI_system_endpoints_handler.cpp index d63fe32..ddc777f 100644 --- a/src/RESTAPI/RESTAPI_systemEndpoints_handler.cpp +++ b/src/RESTAPI/RESTAPI_system_endpoints_handler.cpp @@ -2,12 +2,12 @@ // Created by stephane bourque on 2021-07-01. // -#include "RESTAPI_systemEndpoints_handler.h" +#include "RESTAPI_system_endpoints_handler.h" #include "RESTObjects/RESTAPI_SecurityObjects.h" namespace OpenWifi { - void RESTAPI_systemEndpoints_handler::DoGet() { + void RESTAPI_system_endpoints_handler::DoGet() { auto Services = MicroService::instance().GetServices(); SecurityObjects::SystemEndpointList L; for(const auto &i:Services) { diff --git a/src/RESTAPI/RESTAPI_systemEndpoints_handler.h b/src/RESTAPI/RESTAPI_system_endpoints_handler.h similarity index 66% rename from src/RESTAPI/RESTAPI_systemEndpoints_handler.h rename to src/RESTAPI/RESTAPI_system_endpoints_handler.h index b905242..6a638bb 100644 --- a/src/RESTAPI/RESTAPI_systemEndpoints_handler.h +++ b/src/RESTAPI/RESTAPI_system_endpoints_handler.h @@ -2,15 +2,14 @@ // Created by stephane bourque on 2021-07-01. // -#ifndef UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H -#define UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H +#pragma once #include "../framework/MicroService.h" namespace OpenWifi { - class RESTAPI_systemEndpoints_handler : public RESTAPIHandler { + class RESTAPI_system_endpoints_handler : public RESTAPIHandler { public: - RESTAPI_systemEndpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + RESTAPI_system_endpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) : RESTAPIHandler(bindings, L, std::vector{Poco::Net::HTTPRequest::HTTP_GET, Poco::Net::HTTPRequest::HTTP_OPTIONS}, @@ -23,5 +22,3 @@ namespace OpenWifi { void DoPut() final {}; }; } - -#endif //UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H diff --git a/src/RESTAPI/RESTAPI_user_handler.h b/src/RESTAPI/RESTAPI_user_handler.h index 517d5c5..64e3130 100644 --- a/src/RESTAPI/RESTAPI_user_handler.h +++ b/src/RESTAPI/RESTAPI_user_handler.h @@ -2,8 +2,7 @@ // Created by stephane bourque on 2021-06-21. // -#ifndef UCENTRALSEC_RESTAPI_USER_HANDLER_H -#define UCENTRALSEC_RESTAPI_USER_HANDLER_H +#pragma once #include "framework/MicroService.h" @@ -29,6 +28,3 @@ namespace OpenWifi { }; } - - -#endif //UCENTRALSEC_RESTAPI_USER_HANDLER_H diff --git a/src/RESTAPI/RESTAPI_users_handler.h b/src/RESTAPI/RESTAPI_users_handler.h index 201c39b..68899f9 100644 --- a/src/RESTAPI/RESTAPI_users_handler.h +++ b/src/RESTAPI/RESTAPI_users_handler.h @@ -2,8 +2,7 @@ // Created by stephane bourque on 2021-06-21. // -#ifndef UCENTRALSEC_RESTAPI_USERS_HANDLER_H -#define UCENTRALSEC_RESTAPI_USERS_HANDLER_H +#pragma once #include "framework/MicroService.h" @@ -25,5 +24,3 @@ namespace OpenWifi { }; }; - -#endif //UCENTRALSEC_RESTAPI_USERS_HANDLER_H diff --git a/src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp b/src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp new file mode 100644 index 0000000..5507adc --- /dev/null +++ b/src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp @@ -0,0 +1,26 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "RESTAPI_validate_sub_token_handler.h" +#include "AuthService.h" + +namespace OpenWifi { + void RESTAPI_validate_sub_token_handler::DoGet() { + Poco::URI URI(Request->getURI()); + auto Parameters = URI.getQueryParameters(); + for(auto const &i:Parameters) { + if (i.first == "token") { + // can we find this token? + SecurityObjects::UserInfoAndPolicy SecObj; + bool Expired = false; + if (AuthService()->IsValidSubToken(i.second, SecObj.webtoken, SecObj.userinfo, Expired)) { + Poco::JSON::Object Obj; + SecObj.to_json(Obj); + return ReturnObject(Obj); + } + } + } + return NotFound(); + } +} \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_validate_sub_token_handler.h b/src/RESTAPI/RESTAPI_validate_sub_token_handler.h new file mode 100644 index 0000000..b53ada3 --- /dev/null +++ b/src/RESTAPI/RESTAPI_validate_sub_token_handler.h @@ -0,0 +1,25 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include "framework/MicroService.h" + +namespace OpenWifi { + class RESTAPI_validate_sub_token_handler : public RESTAPIHandler { + public: + RESTAPI_validate_sub_token_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector + {Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, + Internal) {}; + static const std::list PathName() { return std::list{"/api/v1/validateSubToken"}; }; + void DoGet() final; + void DoPost() final {}; + void DoDelete() final {}; + void DoPut() final {}; + }; +} diff --git a/src/RESTAPI/RESTAPI_validateToken_handler.cpp b/src/RESTAPI/RESTAPI_validate_token_handler.cpp similarity index 88% rename from src/RESTAPI/RESTAPI_validateToken_handler.cpp rename to src/RESTAPI/RESTAPI_validate_token_handler.cpp index a937ea1..f92aeb6 100644 --- a/src/RESTAPI/RESTAPI_validateToken_handler.cpp +++ b/src/RESTAPI/RESTAPI_validate_token_handler.cpp @@ -2,11 +2,11 @@ // Created by stephane bourque on 2021-07-01. // -#include "RESTAPI_validateToken_handler.h" +#include "RESTAPI_validate_token_handler.h" #include "AuthService.h" namespace OpenWifi { - void RESTAPI_validateToken_handler::DoGet() { + void RESTAPI_validate_token_handler::DoGet() { Poco::URI URI(Request->getURI()); auto Parameters = URI.getQueryParameters(); for(auto const &i:Parameters) { diff --git a/src/RESTAPI/RESTAPI_validateToken_handler.h b/src/RESTAPI/RESTAPI_validate_token_handler.h similarity index 66% rename from src/RESTAPI/RESTAPI_validateToken_handler.h rename to src/RESTAPI/RESTAPI_validate_token_handler.h index 6475d8e..642e803 100644 --- a/src/RESTAPI/RESTAPI_validateToken_handler.h +++ b/src/RESTAPI/RESTAPI_validate_token_handler.h @@ -2,15 +2,14 @@ // Created by stephane bourque on 2021-07-01. // -#ifndef UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H -#define UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H +#pragma once #include "framework/MicroService.h" namespace OpenWifi { - class RESTAPI_validateToken_handler : public RESTAPIHandler { + class RESTAPI_validate_token_handler : public RESTAPIHandler { public: - RESTAPI_validateToken_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) + RESTAPI_validate_token_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) : RESTAPIHandler(bindings, L, std::vector {Poco::Net::HTTPRequest::HTTP_GET, @@ -25,4 +24,3 @@ namespace OpenWifi { }; } -#endif //UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H diff --git a/src/RESTObjects/RESTAPI_GWobjects.cpp b/src/RESTObjects/RESTAPI_GWobjects.cpp index 507a5ba..24dd4ef 100644 --- a/src/RESTObjects/RESTAPI_GWobjects.cpp +++ b/src/RESTObjects/RESTAPI_GWobjects.cpp @@ -12,6 +12,7 @@ #include "Daemon.h" #ifdef TIP_GATEWAY_SERVICE #include "DeviceRegistry.h" +#include "CapabilitiesCache.h" #endif #include "RESTAPI_GWobjects.h" @@ -26,7 +27,7 @@ namespace OpenWifi::GWObjects { void Device::to_json(Poco::JSON::Object &Obj) const { field_to_json(Obj,"serialNumber", SerialNumber); #ifdef TIP_GATEWAY_SERVICE - field_to_json(Obj,"deviceType", Daemon::instance()->IdentifyDevice(Compatible)); + field_to_json(Obj,"deviceType", CapabilitiesCache::instance()->Get(Compatible)); #endif field_to_json(Obj,"macAddress", MACAddress); field_to_json(Obj,"manufacturer", Manufacturer); diff --git a/src/RESTObjects/RESTAPI_GWobjects.h b/src/RESTObjects/RESTAPI_GWobjects.h index 5f90c9e..5485357 100644 --- a/src/RESTObjects/RESTAPI_GWobjects.h +++ b/src/RESTObjects/RESTAPI_GWobjects.h @@ -6,8 +6,7 @@ // Arilia Wireless Inc. // -#ifndef UCENTRAL_RESTAPI_OBJECTS_H -#define UCENTRAL_RESTAPI_OBJECTS_H +#pragma once #include "Poco/JSON/Object.h" #include "RESTAPI_SecurityObjects.h" @@ -111,7 +110,7 @@ namespace OpenWifi::GWObjects { struct DefaultConfiguration { std::string Name; std::string Configuration; - std::string Models; + Types::StringVec Models; std::string Description; uint64_t Created; uint64_t LastModified; @@ -191,5 +190,3 @@ namespace OpenWifi::GWObjects { void to_json(Poco::JSON::Object &Obj) const; }; } - -#endif //UCENTRAL_RESTAPI_OBJECTS_H diff --git a/src/RESTObjects/RESTAPI_ProvObjects.h b/src/RESTObjects/RESTAPI_ProvObjects.h index 7ee555e..6ac9b4f 100644 --- a/src/RESTObjects/RESTAPI_ProvObjects.h +++ b/src/RESTObjects/RESTAPI_ProvObjects.h @@ -6,9 +6,7 @@ // Arilia Wireless Inc. // - -#ifndef OWPROV_RESTAPI_PROVOBJECTS_H -#define OWPROV_RESTAPI_PROVOBJECTS_H +#pragma once #include #include "RESTAPI_SecurityObjects.h" @@ -380,6 +378,3 @@ namespace OpenWifi::ProvObjects { bool UpdateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I); bool CreateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I); }; - - -#endif //OWPROV_RESTAPI_PROVOBJECTS_H diff --git a/src/StorageService.h b/src/StorageService.h index 1fce650..ec80c02 100644 --- a/src/StorageService.h +++ b/src/StorageService.h @@ -95,6 +95,7 @@ namespace OpenWifi { * All user management functions */ bool InitializeDefaultUser(); + bool CreateUser(const std::string & Admin, SecurityObjects::UserInfo & NewUser, bool PasswordHashedAlready = false); bool GetUserByEmail(std::string & email, SecurityObjects::UserInfo & User); bool GetUserById(USER_ID_TYPE & Id, SecurityObjects::UserInfo & User); @@ -108,6 +109,19 @@ namespace OpenWifi { bool GetUsers( uint64_t Offset, uint64_t Limit, SecurityObjects::UserInfoVec & Users); bool SetLastLogin(USER_ID_TYPE & Id); + bool CreateSubUser(const std::string & Admin, SecurityObjects::UserInfo & NewUser, bool PasswordHashedAlready = false); + bool GetSubUserByEmail(std::string & email, SecurityObjects::UserInfo & User); + bool GetSubUserById(USER_ID_TYPE & Id, SecurityObjects::UserInfo & User); + bool DeleteSubUser(const std::string & Admin, USER_ID_TYPE & Id); + bool SetSubOwner(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Owner); + bool SetSubLocation(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Location); + AUTH_ERROR ChangeSubPassword(const std::string & Admin, USER_ID_TYPE & Id, const std::string &OldPassword, const std::string &NewPassword); + bool AddSubNotes(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Notes); + bool SetSubPolicyChange(const std::string & Admin, USER_ID_TYPE & Id, const std::string &NewPolicy); + bool UpdateSubUserInfo(const std::string & Admin, USER_ID_TYPE & Id, SecurityObjects::UserInfo &UInfo); + bool GetSubUsers( uint64_t Offset, uint64_t Limit, SecurityObjects::UserInfoVec & Users); + bool SetSubLastLogin(USER_ID_TYPE & Id); + bool SetAvatar(const std::string & Admin, std::string &Id, Poco::TemporaryFile &FileName, std::string &Type, std::string & Name); bool GetAvatar(const std::string & Admin, std::string &Id, Poco::TemporaryFile &FileName, std::string &Type, std::string & Name); bool DeleteAvatar(const std::string & Admin, std::string &Id); @@ -119,6 +133,13 @@ namespace OpenWifi { bool RevokeAllTokens( std::string & UserName ); bool GetToken(std::string &Token, SecurityObjects::UserInfoAndPolicy &UInfo, uint64_t &RevocationDate); + bool AddSubToken(std::string &UserId, std::string &Token, std::string &RefreshToken, std::string & TokenType, uint64_t Expires, uint64_t TimeOut); + bool RevokeSubToken( std::string & Token ); + bool IsSubTokenRevoked( std::string & Token ); + bool CleanExpiredSubTokens(); + bool RevokeAllSubTokens( std::string & UserName ); + bool GetSubToken(std::string &Token, SecurityObjects::UserInfoAndPolicy &UInfo, uint64_t &RevocationDate); + /* * All ActionLinks functions */ @@ -142,6 +163,8 @@ namespace OpenWifi { int Create_TokensTable(); int Create_ActionLinkTable(); int Create_Preferences(); + int Create_SubTokensTable(); + int Create_SubscriberTable(); Poco::Timer Timer_; Archiver Archiver_; diff --git a/src/framework/MicroService.h b/src/framework/MicroService.h index b5f337b..57281ec 100644 --- a/src/framework/MicroService.h +++ b/src/framework/MicroService.h @@ -1522,7 +1522,8 @@ namespace OpenWifi { bool Internal=false, bool AlwaysAuthorize=true, bool RateLimited=false, - const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100}) + const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100}, + bool SubscriberOnly=false) : Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), @@ -1530,7 +1531,8 @@ namespace OpenWifi { Internal_(Internal), AlwaysAuthorize_(AlwaysAuthorize), RateLimited_(RateLimited), - MyRates_(Profile){ + MyRates_(Profile), + SubOnlyService_(SubscriberOnly){ } inline bool RoleIsAuthorized(const std::string & Path, const std::string & Method, std::string & Reason) { @@ -1551,7 +1553,7 @@ namespace OpenWifi { return; bool Expired=false; - if (AlwaysAuthorize_ && !IsAuthorized(Expired)) { + if (AlwaysAuthorize_ && !IsAuthorized(Expired, SubOnlyService_)) { if(Expired) return UnAuthorized(RESTAPI::Errors::ExpiredToken, EXPIRED_TOKEN); return UnAuthorized(RESTAPI::Errors::InvalidCredentials, ACCESS_DENIED); @@ -1897,7 +1899,7 @@ namespace OpenWifi { return true; } - inline bool IsAuthorized(bool & Expired); + inline bool IsAuthorized(bool & Expired, bool SubOnly = false ); inline void ReturnObject(Poco::JSON::Object &Object) { PrepareResponse(); @@ -1980,6 +1982,7 @@ namespace OpenWifi { bool Internal_=false; bool RateLimited_=false; bool QueryBlockInitialized_=false; + bool SubOnlyService_=false; Poco::Net::HTTPServerRequest *Request= nullptr; Poco::Net::HTTPServerResponse *Response= nullptr; bool AlwaysAuthorize_=true; @@ -2281,12 +2284,12 @@ namespace OpenWifi { return ((T.expires_in_+T.created_)webtoken)) { @@ -2320,7 +2323,7 @@ namespace OpenWifi { UInfo = *User; return true; } - return RetrieveTokenInformation(SessionToken, UInfo, Expired); + return RetrieveTokenInformation(SessionToken, UInfo, Expired, Sub); } private: @@ -3707,9 +3710,9 @@ namespace OpenWifi { } #ifdef TIP_SECURITY_SERVICE - [[nodiscard]] bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ); + [[nodiscard]] bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired , bool Sub ); #endif - inline bool RESTAPIHandler::IsAuthorized( bool & Expired ) { + inline bool RESTAPIHandler::IsAuthorized( bool & Expired , bool Sub ) { if(Internal_) { auto Allowed = MicroService::instance().IsValidAPIKEY(*Request); if(!Allowed) { @@ -3739,9 +3742,9 @@ namespace OpenWifi { } } #ifdef TIP_SECURITY_SERVICE - if (AuthServiceIsAuthorized(*Request, SessionToken_, UserInfo_, Expired)) { + if (AuthServiceIsAuthorized(*Request, SessionToken_, UserInfo_, Expired, Sub)) { #else - if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, Expired)) { + if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, Expired, Sub)) { #endif if(Server_.LogIt(Request->getMethod(),true)) { Logger_.debug(Poco::format("X-REQ-ALLOWED(%s): User='%s@%s' Method='%s' Path='%s", diff --git a/src/storage/storage_conversions.cpp b/src/storage/storage_conversions.cpp new file mode 100644 index 0000000..163bb2f --- /dev/null +++ b/src/storage/storage_conversions.cpp @@ -0,0 +1,5 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "storage_conversions.h" diff --git a/src/storage/storage_conversions.h b/src/storage/storage_conversions.h new file mode 100644 index 0000000..6ed92f9 --- /dev/null +++ b/src/storage/storage_conversions.h @@ -0,0 +1,77 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once +#include "framework/MicroService.h" +#include "RESTObjects/RESTAPI_SecurityObjects.h" + +namespace OpenWifi { + inline bool Convert(const UserInfoRecord &T, SecurityObjects::UserInfo &U) { + U.Id = T.get<0>(); + U.name = T.get<1>(); + U.description = T.get<2>(); + U.avatar = T.get<3>(); + U.email = T.get<4>(); + U.validated = T.get<5>(); + U.validationEmail = T.get<6>(); + U.validationDate = T.get<7>(); + U.creationDate = T.get<8>(); + U.validationURI = T.get<9>(); + U.changePassword = T.get<10>(); + U.lastLogin = T.get<11>(); + U.currentLoginURI = T.get<12>(); + U.lastPasswordChange = T.get<13>(); + U.lastEmailCheck = T.get<14>(); + U.waitingForEmailCheck = T.get<15>(); + U.locale = T.get<16>(); + U.notes = RESTAPI_utils::to_object_array(T.get<17>()); + U.location = T.get<18>(); + U.owner = T.get<19>(); + U.suspended = T.get<20>(); + U.blackListed = T.get<21>(); + U.userRole = SecurityObjects::UserTypeFromString(T.get<22>()); + U.userTypeProprietaryInfo = RESTAPI_utils::to_object(T.get<23>()); + U.securityPolicy = T.get<24>(); + U.securityPolicyChange = T.get<25>(); + U.currentPassword = T.get<26>(); + U.lastPasswords = RESTAPI_utils::to_object_array(T.get<27>()); + U.oauthType = T.get<28>(); + U.oauthUserInfo = T.get<29>(); + return true; + } + + inline bool Convert(const SecurityObjects::UserInfo &U, UserInfoRecord &T) { + T.set<0>(U.Id); + T.set<1>(U.name); + T.set<2>(U.description); + T.set<3>(U.avatar); + T.set<4>(U.email); + T.set<5>(U.validated); + T.set<6>(U.validationEmail); + T.set<7>(U.validationDate); + T.set<8>(U.creationDate); + T.set<9>(U.validationURI); + T.set<10>(U.changePassword); + T.set<11>(U.lastLogin); + T.set<12>(U.currentLoginURI); + T.set<13>(U.lastPasswordChange); + T.set<14>(U.lastEmailCheck); + T.set<15>(U.waitingForEmailCheck); + T.set<16>(U.locale); + T.set<17>(RESTAPI_utils::to_string(U.notes)); + T.set<18>(U.location); + T.set<19>(U.owner); + T.set<20>(U.suspended); + T.set<21>(U.blackListed); + T.set<22>(SecurityObjects::UserTypeToString(U.userRole)); + T.set<23>(RESTAPI_utils::to_string(U.userTypeProprietaryInfo)); + T.set<24>(U.securityPolicy); + T.set<25>(U.securityPolicyChange); + T.set<26>(U.currentPassword); + T.set<27>(RESTAPI_utils::to_string(U.lastPasswords)); + T.set<28>(U.oauthType); + T.set<29>(U.oauthUserInfo); + return true; + } +} diff --git a/src/storage/storage_subscribers.cpp b/src/storage/storage_subscribers.cpp new file mode 100644 index 0000000..b83de20 --- /dev/null +++ b/src/storage/storage_subscribers.cpp @@ -0,0 +1,283 @@ +// +// Created by stephane bourque on 2021-11-30. +// + + +#include + +#include "Poco/Tuple.h" + +#include "storage_subscribers.h" +#include "StorageService.h" +#include "framework/MicroService.h" +#include "storage/storage_conversions.h" + +namespace OpenWifi { + + bool Storage::CreateSubUser(const std::string & Admin, SecurityObjects::UserInfo & NewUser, bool PasswordHashedAlready ) { + try { + Poco::Data::Session Sess = Pool_->get(); + + Poco::toLowerInPlace(NewUser.email); + + // if the user exists, must return an error + std::string St1{"select " + AllSubUsersFieldsForSelect + " from Subscribers where email=?"}; + UserInfoRecordList Records; + + try { + Poco::Data::Statement Statement(Sess); + Statement << ConvertParams(St1), + Poco::Data::Keywords::into(Records), + Poco::Data::Keywords::use(NewUser.email); + Statement.execute(); + } catch (const Poco::Exception &E) { + + } + + if(!Records.empty()) + return false; + + if(!PasswordHashedAlready) { + NewUser.Id = MicroService::CreateUUID(); + NewUser.creationDate = std::time(nullptr); + } + + // if there is a password, we assume that we do not want email verification, + // if there is no password, we will do email verification + if(NewUser.currentPassword.empty()) { + + } else { + if(!PasswordHashedAlready) { + NewUser.currentPassword = AuthService()->ComputeNewPasswordHash(NewUser.email,NewUser.currentPassword); + NewUser.lastPasswords.clear(); + NewUser.lastPasswords.push_back(NewUser.currentPassword); + NewUser.lastPasswordChange = std::time(nullptr); + NewUser.validated = true; + } + } + + auto Notes = RESTAPI_utils::to_string(NewUser.notes); + auto UserType = SecurityObjects::UserTypeToString(NewUser.userRole); + auto OldPasswords = RESTAPI_utils::to_string(NewUser.lastPasswords); + auto userTypeProprietaryInfo = RESTAPI_utils::to_string(NewUser.userTypeProprietaryInfo); + + St1 = "INSERT INTO Subscribers (" + AllSubUsersFieldsForSelect + ") VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + Poco::Data::Statement Statement(Sess); + + UserInfoRecord R; + Convert(NewUser, R); + + Statement << ConvertParams(St1), + Poco::Data::Keywords::use(R); + Statement.execute(); + return true; + + } catch (const Poco::Exception &E) { + std::cout << "What: " << E.what() << " name: " << E.name() << std::endl; + Logger_.log(E); + } + return false; + } + + bool Storage::GetSubUserByEmail(std::string & email, SecurityObjects::UserInfo & User) { + std::string St1; + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + + Poco::toLowerInPlace(email); + + // if the user exists, must return an error + St1 = "select " + AllSubUsersFieldsForSelect + " from Subscribers where email=?"; + UserInfoRecordList Records; + + Select << ConvertParams(St1) , + Poco::Data::Keywords::into(Records), + Poco::Data::Keywords::use(email); + Select.execute(); + + if(Records.empty()) + return false; + + Convert(Records[0],User); + + return true; + } catch (const Poco::Exception &E) { + std::cout << "Statement: " << St1 << std::endl; + std::cout << "What:" << E.what() << " name: " << E.name() << std::endl; + Logger_.log(E); + } + return false; + } + + bool Storage::GetSubUserById(std::string &Id, SecurityObjects::UserInfo &User) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + + // if the user exists, must return an error + std::string St1{"select " + AllSubUsersFieldsForSelect + " from Subscribers where id=?"}; + UserInfoRecordList Records; + + Select << ConvertParams(St1) , + Poco::Data::Keywords::into(Records), + Poco::Data::Keywords::use(Id); + Select.execute(); + + if(Records.empty()) + return false; + + Convert(Records[0],User); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::GetSubUsers( uint64_t Offset, uint64_t HowMany, SecurityObjects::UserInfoVec & Users) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + UserInfoRecordList Records; + + std::string St1{"select " + AllSubUsersFieldsForSelect + " from Subscribers order by id ASC "}; + + Select << ConvertParams(St1) + ComputeRange(Offset, HowMany), + Poco::Data::Keywords::into(Records); + Select.execute(); + + for(const auto &R:Records) { + SecurityObjects::UserInfo U; + Convert(R,U); + Users.push_back(U); + } + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::UpdateSubUserInfo(const std::string & Admin, USER_ID_TYPE & Id, SecurityObjects::UserInfo &UInfo) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + std::string St1{"update Subscribers set " + AllSubUsersFieldsForUpdate + " where id=?"}; + auto Notes = RESTAPI_utils::to_string(UInfo.notes); + auto UserType = SecurityObjects::UserTypeToString(UInfo.userRole); + auto OldPasswords = RESTAPI_utils::to_string(UInfo.lastPasswords); + auto userTypeProprietaryInfo = RESTAPI_utils::to_string(UInfo.userTypeProprietaryInfo); + UserInfoRecord R; + Convert(UInfo, R); + Update << ConvertParams(St1), + Poco::Data::Keywords::use(R), + Poco::Data::Keywords::use(UInfo.Id); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + std::cout << " Exception: " << E.what() << " name: " << E.name() << std::endl; + Logger_.log(E); + } + return false; + } + + bool Storage::DeleteSubUser(const std::string & Admin, USER_ID_TYPE & Id) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Delete(Sess); + + std::string St1{"delete from Subscribers where id=?"}; + + Delete << ConvertParams(St1), + Poco::Data::Keywords::use(Id); + Delete.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::SetSubOwner(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Owner) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::SetSubLocation(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Location) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::SetSubLastLogin(std::string &Id) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + std::string St1{"update Subscribers set lastLogin=? where id=?"}; + uint64_t Now=std::time(nullptr); + Update << ConvertParams(St1), + Poco::Data::Keywords::use(Now), + Poco::Data::Keywords::use(Id); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + Storage::AUTH_ERROR Storage::ChangeSubPassword(const std::string & Admin, USER_ID_TYPE & Id, const std::string &OldPassword, const std::string &NewPassword) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + + return SUCCESS; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return INTERNAL_ERROR; + } + + bool Storage::AddSubNotes(const std::string & Admin, USER_ID_TYPE & Id, const std::string &Notes) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::SetSubPolicyChange(const std::string & Admin, USER_ID_TYPE & Id, const std::string &NewPolicy) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + +} + diff --git a/src/storage/storage_subscribers.h b/src/storage/storage_subscribers.h new file mode 100644 index 0000000..5ae1d99 --- /dev/null +++ b/src/storage/storage_subscribers.h @@ -0,0 +1,142 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include +#include +#include "Poco/Tuple.h" + +namespace OpenWifi { + static const std::string AllSubUsersFieldsForCreation{ + " Id varchar(36) UNIQUE PRIMARY KEY," + "name varchar," + "description varchar," + "avatar varchar," + "email varchar," + "validated int," + "validationEmail varchar," + "validationDate bigint," + "creationDate bigint," + "validationURI varchar," + "changePassword int," + "lastLogin bigint," + "currentLoginURI varchar," + "lastPasswordChange bigint," + "lastEmailCheck bigint," + "waitingForEmailCheck int," + "locale varchar," + "notes text," + "location varchar," + "owner varchar," + "suspended int," + "blackListed int," + "userRole varchar," + "userTypeProprietaryInfo text," + "securityPolicy text," + "securityPolicyChange bigint," + "currentPassword varchar," + "lastPasswords varchar," + "oauthType varchar," + "oauthUserInfo text"}; + + static const std::string AllSubUsersFieldsForSelect{ + "Id," + "name," + "description," + "avatar," + "email," + "validated," + "validationEmail," + "validationDate," + "creationDate," + "validationURI," + "changePassword," + "lastLogin," + "currentLoginURI," + "lastPasswordChange," + "lastEmailCheck," + "waitingForEmailCheck," + "locale," + "notes," + "location," + "owner," + "suspended," + "blackListed," + "userRole," + "userTypeProprietaryInfo," + "securityPolicy," + "securityPolicyChange," + "currentPassword," + "lastPasswords," + "oauthType," + "oauthUserInfo"}; + + static const std::string AllSubUsersFieldsForUpdate{ + " Id=?, " + "name=?, " + "description=?, " + "avatar=?, " + "email=?, " + "validated=?, " + "validationEmail=?, " + "validationDate=?, " + "creationDate=?, " + "validationURI=?, " + "changePassword=?, " + "lastLogin=?, " + "currentLoginURI=?, " + "lastPasswordChange=?, " + "lastEmailCheck=?, " + "waitingForEmailCheck=?, " + "locale=?, " + "notes=?, " + "location=?, " + "owner=?, " + "suspended=?, " + "blackListed=?, " + "userRole=?, " + "userTypeProprietaryInfo=?, " + "securityPolicy=?, " + "securityPolicyChange=?, " + "currentPassword=?, " + "lastPasswords=?, " + "oauthType=?, " + "oauthUserInfo=? "}; + + typedef Poco::Tuple < + std::string, // Id = 0; + std::string, // name; + std::string, // description; + std::string, // avatar; + std::string, // email; + uint64_t, // bool validated = false; + std::string, // validationEmail; + uint64_t, // validationDate = 0; + uint64_t, // creationDate = 0; + std::string, // validationURI; + uint64_t, // bool changePassword = true; + uint64_t, // lastLogin = 0; + std::string, // currentLoginURI; + uint64_t, // lastPasswordChange = 0; + uint64_t, // lastEmailCheck = 0; + uint64_t, // bool waitingForEmailCheck = false; + std::string, // locale; + std::string, // notes; + std::string, // location; + std::string, // owner; + uint64_t, // bool suspended = false; + uint64_t, // bool blackListed = false; + std::string, // userRole; + std::string, // userTypeProprietaryInfo; + std::string, // securityPolicy; + uint64_t, // securityPolicyChange; + std::string, // currentPassword; + std::string, // lastPasswords; + std::string, // oauthType; + std::string // oauthUserInfo; + > UserInfoRecord; + + typedef std::vector UserInfoRecordList; +} diff --git a/src/storage/storage_subtokens.cpp b/src/storage/storage_subtokens.cpp new file mode 100644 index 0000000..1518027 --- /dev/null +++ b/src/storage/storage_subtokens.cpp @@ -0,0 +1,150 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#include "storage/storage_subtokens.h" +#include "StorageService.h" + +namespace OpenWifi { + + /* + "Token TEXT PRIMARY KEY, " + "RefreshToken TEXT, " + "TokenType TEXT, " + "UserName TEXT, " + "Created BIGINT, " + "Expires BIGINT, " + "IdleTimeOut BIGINT, " + "RevocationDate BIGINT " + */ + + bool Storage::AddSubToken(std::string &UserID, std::string &Token, std::string &RefreshToken, std::string & TokenType, uint64_t Expires, uint64_t TimeOut) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Insert(Sess); + uint64_t Now = std::time(nullptr); + uint64_t Z = 0; + + std::string St2{ + "INSERT INTO SubTokens (" + AllSubTokensFieldsForSelect + ") VALUES(" + AllSubTokensValuesForSelect + ")"}; + + Insert << ConvertParams(St2), + Poco::Data::Keywords::use(Token), + Poco::Data::Keywords::use(RefreshToken), + Poco::Data::Keywords::use(TokenType), + Poco::Data::Keywords::use(UserID), + Poco::Data::Keywords::use(Now), + Poco::Data::Keywords::use(Expires), + Poco::Data::Keywords::use(TimeOut), + Poco::Data::Keywords::use(Z); + Insert.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::GetSubToken(std::string &Token, SecurityObjects::UserInfoAndPolicy &UInfo, uint64_t &RevocationDate) { + try { + + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + RevocationDate = 0 ; + std::string St2{"SELECT " + AllSubTokensFieldsForSelect + " From SubTokens WHERE Token=?"}; + Select << ConvertParams(St2), + Poco::Data::Keywords::into(UInfo.webtoken.access_token_), + Poco::Data::Keywords::into(UInfo.webtoken.refresh_token_), + Poco::Data::Keywords::into(UInfo.webtoken.token_type_), + Poco::Data::Keywords::into(UInfo.userinfo.Id), + Poco::Data::Keywords::into(UInfo.webtoken.created_), + Poco::Data::Keywords::into(UInfo.webtoken.expires_in_), + Poco::Data::Keywords::into(UInfo.webtoken.idle_timeout_), + Poco::Data::Keywords::into(RevocationDate), + Poco::Data::Keywords::use(Token); + Select.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::IsSubTokenRevoked(std::string &Token) { + try { + + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Select(Sess); + + uint32_t RevocationDate = 0 ; + + std::string St2{"SELECT RevocationDate From SubTokens WHERE Token=?"}; + Select << ConvertParams(St2), + Poco::Data::Keywords::into(RevocationDate), + Poco::Data::Keywords::use(Token); + Select.execute(); + + if(Select.columnsExtracted()==0) + return false; + return RevocationDate>0 ; + + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::RevokeSubToken(std::string &Token) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Update(Sess); + + uint64_t Now = std::time(nullptr); + + // update users set lastLogin=? where id=? + std::string St2{"UPDATE SubTokens Set RevocationDate=? WHERE Token=?"}; + Update << ConvertParams(St2), + Poco::Data::Keywords::use(Now), + Poco::Data::Keywords::use(Token); + Update.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::CleanExpiredSubTokens() { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Delete(Sess); + uint64_t Now = std::time(nullptr); + + std::string St2{"DELETE From SubTokens WHERE (Created+Expires) <= ?"}; + Delete << ConvertParams(St2), + Poco::Data::Keywords::use(Now); + Delete.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Storage::RevokeAllSubTokens(std::string & UserId) { + try { + Poco::Data::Session Sess = Pool_->get(); + Poco::Data::Statement Delete(Sess); + + std::string St2{"DELETE SubFrom Tokens WHERE Username=?"}; + Delete << ConvertParams(St2), + Poco::Data::Keywords::use(UserId); + Delete.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_subtokens.h b/src/storage/storage_subtokens.h new file mode 100644 index 0000000..931315a --- /dev/null +++ b/src/storage/storage_subtokens.h @@ -0,0 +1,27 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include +#include +#include "Poco/Tuple.h" + +namespace OpenWifi { + + static std::string AllSubTokensFieldsForCreation{ "Token TEXT PRIMARY KEY, " + "RefreshToken TEXT, " + "TokenType TEXT, " + "UserName TEXT, " + "Created BIGINT, " + "Expires BIGINT, " + "IdleTimeOut BIGINT, " + "RevocationDate BIGINT " + }; + static std::string AllSubTokensFieldsForSelect {"Token, RefreshToken, TokenType, Username, Created, Expires, IdleTimeOut, RevocationDate"}; + static std::string AllSubTokensValuesForSelect{"?,?,?,?,?,?,?,?"}; + + + +} diff --git a/src/storage/storage_tables.cpp b/src/storage/storage_tables.cpp index fc6296c..02a0d06 100644 --- a/src/storage/storage_tables.cpp +++ b/src/storage/storage_tables.cpp @@ -17,6 +17,8 @@ namespace OpenWifi { Create_TokensTable(); Create_ActionLinkTable(); Create_Preferences(); + Create_SubTokensTable(); + Create_SubscriberTable(); return 0; } @@ -45,6 +47,31 @@ namespace OpenWifi { return 1; } + int Storage::Create_SubscriberTable() { + Poco::Data::Session Sess = Pool_->get(); + + try { + if (dbType_ == mysql) { + Sess << "CREATE TABLE IF NOT EXISTS Subscribers (" + + AllUsersFieldsForCreation + + " ,INDEX emailindex (email ASC)" + " ,INDEX nameindex (name ASC))", + Poco::Data::Keywords::now; + } else { + Sess << "CREATE TABLE IF NOT EXISTS Subscribers (" + + AllUsersFieldsForCreation + + ")", + Poco::Data::Keywords::now; + Sess << "CREATE INDEX IF NOT EXISTS emailindex ON Subscribers (email ASC)", Poco::Data::Keywords::now; + Sess << "CREATE INDEX IF NOT EXISTS nameindex ON Subscribers (name ASC)", Poco::Data::Keywords::now; + } + return 0; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return 1; + } + int Storage::Create_ActionLinkTable() { try { Poco::Data::Session Sess = Pool_->get(); @@ -93,6 +120,19 @@ namespace OpenWifi { return 1; } + int Storage::Create_SubTokensTable() { + try { + Poco::Data::Session Sess = Pool_->get(); + Sess << "CREATE TABLE IF NOT EXISTS SubTokens (" + + AllTokensFieldsForCreation + + ") ", Poco::Data::Keywords::now; + return 0; + } catch(const Poco::Exception &E) { + Logger_.log(E); + } + return 1; + } + int Storage::Create_Preferences() { try { Poco::Data::Session Sess = Pool_->get(); diff --git a/src/storage/storage_users.cpp b/src/storage/storage_users.cpp index 9de9b27..03ae28b 100644 --- a/src/storage/storage_users.cpp +++ b/src/storage/storage_users.cpp @@ -9,77 +9,10 @@ #include "StorageService.h" #include "framework/MicroService.h" +#include "storage/storage_conversions.h" namespace OpenWifi { - bool Convert(const UserInfoRecord &T, SecurityObjects::UserInfo &U) { - U.Id = T.get<0>(); - U.name = T.get<1>(); - U.description = T.get<2>(); - U.avatar = T.get<3>(); - U.email = T.get<4>(); - U.validated = T.get<5>(); - U.validationEmail = T.get<6>(); - U.validationDate = T.get<7>(); - U.creationDate = T.get<8>(); - U.validationURI = T.get<9>(); - U.changePassword = T.get<10>(); - U.lastLogin = T.get<11>(); - U.currentLoginURI = T.get<12>(); - U.lastPasswordChange = T.get<13>(); - U.lastEmailCheck = T.get<14>(); - U.waitingForEmailCheck = T.get<15>(); - U.locale = T.get<16>(); - U.notes = RESTAPI_utils::to_object_array(T.get<17>()); - U.location = T.get<18>(); - U.owner = T.get<19>(); - U.suspended = T.get<20>(); - U.blackListed = T.get<21>(); - U.userRole = SecurityObjects::UserTypeFromString(T.get<22>()); - U.userTypeProprietaryInfo = RESTAPI_utils::to_object(T.get<23>()); - U.securityPolicy = T.get<24>(); - U.securityPolicyChange = T.get<25>(); - U.currentPassword = T.get<26>(); - U.lastPasswords = RESTAPI_utils::to_object_array(T.get<27>()); - U.oauthType = T.get<28>(); - U.oauthUserInfo = T.get<29>(); - return true; - } - - bool Convert(const SecurityObjects::UserInfo &U, UserInfoRecord &T) { - T.set<0>(U.Id); - T.set<1>(U.name); - T.set<2>(U.description); - T.set<3>(U.avatar); - T.set<4>(U.email); - T.set<5>(U.validated); - T.set<6>(U.validationEmail); - T.set<7>(U.validationDate); - T.set<8>(U.creationDate); - T.set<9>(U.validationURI); - T.set<10>(U.changePassword); - T.set<11>(U.lastLogin); - T.set<12>(U.currentLoginURI); - T.set<13>(U.lastPasswordChange); - T.set<14>(U.lastEmailCheck); - T.set<15>(U.waitingForEmailCheck); - T.set<16>(U.locale); - T.set<17>(RESTAPI_utils::to_string(U.notes)); - T.set<18>(U.location); - T.set<19>(U.owner); - T.set<20>(U.suspended); - T.set<21>(U.blackListed); - T.set<22>(SecurityObjects::UserTypeToString(U.userRole)); - T.set<23>(RESTAPI_utils::to_string(U.userTypeProprietaryInfo)); - T.set<24>(U.securityPolicy); - T.set<25>(U.securityPolicyChange); - T.set<26>(U.currentPassword); - T.set<27>(RESTAPI_utils::to_string(U.lastPasswords)); - T.set<28>(U.oauthType); - T.set<29>(U.oauthUserInfo); - return true; - } - std::string OldDefaultUseridStockUUID{"DEFAULT-USER-UUID-SHOULD-BE-DELETED!!!"}; std::string NewDefaultUseridStockUUID{"11111111-0000-0000-6666-999999999999"}; diff --git a/src/storage/storage_users.h b/src/storage/storage_users.h index 1beba8b..9390f43 100644 --- a/src/storage/storage_users.h +++ b/src/storage/storage_users.h @@ -2,11 +2,7 @@ // Created by stephane bourque on 2021-07-15. // -#ifndef UCENTRALSEC_STORAGE_USERS_H -#define UCENTRALSEC_STORAGE_USERS_H - -#include -#include +#pragma once namespace OpenWifi { static const std::string AllUsersFieldsForCreation{ @@ -140,4 +136,3 @@ namespace OpenWifi { typedef std::vector UserInfoRecordList; } -#endif //UCENTRALSEC_STORAGE_USERS_H