diff --git a/data/sql/updates/pending_db_auth/rev_1781863824915803400.sql b/data/sql/updates/pending_db_auth/rev_1781863824915803400.sql new file mode 100644 index 000000000..50db77464 --- /dev/null +++ b/data/sql/updates/pending_db_auth/rev_1781863824915803400.sql @@ -0,0 +1,13 @@ +DELETE FROM `rbac_permissions` WHERE `id` IN (941, 942, 943, 944); +INSERT INTO `rbac_permissions` (`id`, `name`) VALUES +(941, 'Command: account flag'), +(942, 'Command: account flag list'), +(943, 'Command: account flag add'), +(944, 'Command: account flag remove'); + +DELETE FROM `rbac_linked_permissions` WHERE `linkedId` IN (941, 942, 943, 944); +INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES +(197, 941), +(197, 942), +(196, 943), +(196, 944); diff --git a/data/sql/updates/pending_db_world/rev_1781863825147501000.sql b/data/sql/updates/pending_db_world/rev_1781863825147501000.sql new file mode 100644 index 000000000..851b30ae4 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781863825147501000.sql @@ -0,0 +1,15 @@ +DELETE FROM `command` WHERE `name` IN ('account flag', 'account flag list', 'account flag add', 'account flag remove'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('account flag', 2, 'Syntax: .account flag $subcommand\nType .account flag to see the list of possible subcommands or .help account flag $subcommand to see info on subcommands.'), +('account flag list', 2, 'Syntax: .account flag list [$account]\nLists every AccountFlag currently set on $account. Defaults to your own account when no argument is given.'), +('account flag add', 3, 'Syntax: .account flag add $account $flag\nSets an AccountFlag on $account. $flag is a symbolic name in either its full form (e.g. ACCOUNT_FLAG_TRIAL) or short form (e.g. TRIAL). ACCOUNT_FLAG_GM is managed automatically and cannot be changed manually.'), +('account flag remove', 3, 'Syntax: .account flag remove $account $flag\nClears an AccountFlag on $account. $flag is a symbolic name in either its full form (e.g. ACCOUNT_FLAG_TRIAL) or short form (e.g. TRIAL). ACCOUNT_FLAG_GM is managed automatically and cannot be changed manually.'); + +DELETE FROM `acore_string` WHERE `entry` IN (35465, 35466, 35467, 35468, 35469, 35470); +INSERT INTO `acore_string` (`entry`, `content_default`) VALUES +(35465, 'Account flags set on {} ({}):'), +(35466, 'Account {} ({}) has no flags set.'), +(35467, 'Unknown account flag: {}. Use a symbolic name (e.g. ACCOUNT_FLAG_TRIAL or TRIAL).'), +(35468, 'ACCOUNT_FLAG_GM is managed automatically and cannot be changed manually.'), +(35469, 'Added {} to account {} ({}).'), +(35470, 'Removed {} from account {} ({}).'); diff --git a/src/common/Common.cpp b/src/common/Common.cpp index 9d7835248..e58668d7e 100644 --- a/src/common/Common.cpp +++ b/src/common/Common.cpp @@ -30,6 +30,42 @@ char const* localeNames[TOTAL_LOCALES] = "ruRU" }; +AccountFlagName const accountFlagNames[MAX_ACCOUNT_FLAG] = +{ + { "ACCOUNT_FLAG_GM", "GM" }, + { "ACCOUNT_FLAG_NOKICK", "NOKICK" }, + { "ACCOUNT_FLAG_COLLECTOR", "COLLECTOR" }, + { "ACCOUNT_FLAG_TRIAL", "TRIAL" }, + { "ACCOUNT_FLAG_CANCELLED", "CANCELLED" }, + { "ACCOUNT_FLAG_IGR", "IGR" }, + { "ACCOUNT_FLAG_WHOLESALER", "WHOLESALER" }, + { "ACCOUNT_FLAG_PRIVILEGED", "PRIVILEGED" }, + { "ACCOUNT_FLAG_EU_FORBID_ELV", "EU_FORBID_ELV" }, + { "ACCOUNT_FLAG_EU_FORBID_BILLING", "EU_FORBID_BILLING" }, + { "ACCOUNT_FLAG_RESTRICTED", "RESTRICTED" }, + { "ACCOUNT_FLAG_REFERRAL", "REFERRAL" }, + { "ACCOUNT_FLAG_BLIZZARD", "BLIZZARD" }, + { "ACCOUNT_FLAG_RECURRING_BILLING", "RECURRING_BILLING" }, + { "ACCOUNT_FLAG_NOELECTUP", "NOELECTUP" }, + { "ACCOUNT_FLAG_KR_CERTIFICATE", "KR_CERTIFICATE" }, + { "ACCOUNT_FLAG_EXPANSION_COLLECTOR", "EXPANSION_COLLECTOR" }, + { "ACCOUNT_FLAG_DISABLE_VOICE", "DISABLE_VOICE" }, + { "ACCOUNT_FLAG_DISABLE_VOICE_SPEAK", "DISABLE_VOICE_SPEAK" }, + { "ACCOUNT_FLAG_REFERRAL_RESURRECT", "REFERRAL_RESURRECT" }, + { "ACCOUNT_FLAG_EU_FORBID_CC", "EU_FORBID_CC" }, + { "ACCOUNT_FLAG_OPENBETA_DELL", "OPENBETA_DELL" }, + { "ACCOUNT_FLAG_PROPASS", "PROPASS" }, + { "ACCOUNT_FLAG_PROPASS_LOCK", "PROPASS_LOCK" }, + { "ACCOUNT_FLAG_PENDING_UPGRADE", "PENDING_UPGRADE" }, + { "ACCOUNT_FLAG_RETAIL_FROM_TRIAL", "RETAIL_FROM_TRIAL" }, + { "ACCOUNT_FLAG_EXPANSION2_COLLECTOR", "EXPANSION2_COLLECTOR" }, + { "ACCOUNT_FLAG_OVERMIND_LINKED", "OVERMIND_LINKED" }, + { "ACCOUNT_FLAG_DEMOS", "DEMOS" }, + { "ACCOUNT_FLAG_DEATH_KNIGHT_OK", "DEATH_KNIGHT_OK" }, + { "ACCOUNT_FLAG_S2_REQUIRE_IGR", "S2_REQUIRE_IGR" }, + { "ACCOUNT_FLAG_S2_TRIAL", "S2_TRIAL" } +}; + bool IsLocaleValid(std::string const& locale) { for (int i = 0; i < TOTAL_LOCALES; ++i) diff --git a/src/common/Common.h b/src/common/Common.h index b77fe3760..908e5aca6 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -113,6 +113,14 @@ constexpr uint32 ACCOUNT_FLAGS_ALL = ACCOUNT_FLAG_OVERMIND_LINKED | ACCOUNT_FLAG_DEMOS | ACCOUNT_FLAG_DEATH_KNIGHT_OK | ACCOUNT_FLAG_S2_REQUIRE_IGR | ACCOUNT_FLAG_S2_TRIAL; +struct AccountFlagName +{ + char const* full; + char const* shortName; +}; + +AC_COMMON_API extern AccountFlagName const accountFlagNames[MAX_ACCOUNT_FLAG]; + enum LocaleConstant { LOCALE_enUS = 0, diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index ec4b71654..58481ef8d 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -702,6 +702,10 @@ enum RBACPermissions RBAC_PERM_COMMAND_SPELLINFO_ALL = 938, RBAC_PERM_COMMAND_SERVER_SET_SECURITY = 939, RBAC_PERM_COMMAND_GROUP_INVITES = 940, + RBAC_PERM_COMMAND_ACCOUNT_FLAG = 941, + RBAC_PERM_COMMAND_ACCOUNT_FLAG_LIST = 942, + RBAC_PERM_COMMAND_ACCOUNT_FLAG_ADD = 943, + RBAC_PERM_COMMAND_ACCOUNT_FLAG_REMOVE = 944, // custom permissions 1000+ RBAC_PERM_MAX }; diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 26330f93e..b405d0e24 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1537,7 +1537,14 @@ enum AcoreStrings // Group invites toggle command LANG_COMMAND_GROUP_INVITES_ACCEPTING = 35462, LANG_COMMAND_GROUP_INVITES_ON = 35463, - LANG_COMMAND_GROUP_INVITES_OFF = 35464 + LANG_COMMAND_GROUP_INVITES_OFF = 35464, + // Account flag command + LANG_ACCOUNT_FLAG_LIST_HEADER = 35465, + LANG_ACCOUNT_FLAG_LIST_EMPTY = 35466, + LANG_ACCOUNT_FLAG_INVALID = 35467, + LANG_ACCOUNT_FLAG_RESERVED = 35468, + LANG_ACCOUNT_FLAG_ADDED = 35469, + LANG_ACCOUNT_FLAG_REMOVED = 35470 }; #endif diff --git a/src/server/scripts/Commands/cs_account.cpp b/src/server/scripts/Commands/cs_account.cpp index 9c37eff54..3a75acf66 100644 --- a/src/server/scripts/Commands/cs_account.cpp +++ b/src/server/scripts/Commands/cs_account.cpp @@ -20,8 +20,10 @@ #include "Base32.h" #include "Chat.h" #include "CommandScript.h" +#include "Common.h" #include "CryptoGenerics.h" #include "IPLocation.h" +#include "Language.h" #include "Player.h" #include "RBAC.h" #include "Realm.h" @@ -29,6 +31,9 @@ #include "SecretMgr.h" #include "StringConvert.h" #include "TOTP.h" +#include "Util.h" +#include "WorldSession.h" +#include "WorldSessionMgr.h" #include #if AC_COMPILER == AC_COMPILER_GNU @@ -70,12 +75,20 @@ public: { "country", HandleAccountRemoveLockCountryCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_LOCK_COUNTRY, Console::Yes }, }; + static ChatCommandTable accountFlagCommandTable + { + { "list", HandleAccountFlagListCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_FLAG_LIST, Console::Yes }, + { "add", HandleAccountFlagAddCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_FLAG_ADD, Console::Yes }, + { "remove", HandleAccountFlagRemoveCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_FLAG_REMOVE, Console::Yes } + }; + static ChatCommandTable accountCommandTable = { { "2fa", account2faCommandTable }, { "addon", HandleAccountAddonCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_ADDON, Console::No }, { "create", HandleAccountCreateCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_CREATE, Console::Yes }, { "delete", HandleAccountDeleteCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_DELETE, Console::Yes }, + { "flag", accountFlagCommandTable }, { "onlinelist", HandleAccountOnlineListCommand, rbac::RBAC_PERM_COMMAND_ACCOUNT_ONLINE_LIST, Console::Yes }, { "lock", accountLockCommandTable }, { "set", accountSetCommandTable }, @@ -911,6 +924,111 @@ public: return true; } + static Optional ParseAccountFlagBit(std::string_view input) + { + for (uint8 i = 0; i < MAX_ACCOUNT_FLAG; ++i) + if (StringEqualI(input, accountFlagNames[i].full) || StringEqualI(input, accountFlagNames[i].shortName)) + return i; + + return std::nullopt; + } + + static bool HandleAccountFlagListCommand(ChatHandler* handler, Optional account) + { + uint32 accountId; + std::string accountName; + + if (account) + { + accountId = account->GetID(); + accountName = account->GetName(); + } + else if (WorldSession* session = handler->GetSession()) + { + accountId = session->GetAccountId(); + AccountMgr::GetName(accountId, accountName); + } + else + { + handler->SendErrorMessage(LANG_CMD_SYNTAX); + return false; + } + + if (handler->HasLowerSecurityAccount(nullptr, accountId, true)) + return false; + + uint32 flags; + if (WorldSession* targetSession = sWorldSessionMgr->FindSession(accountId)) + flags = targetSession->GetAccountFlags(); + else + { + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_FLAG); + stmt->SetData(0, accountId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + if (!result) + { + handler->SendErrorMessage(LANG_ACCOUNT_NOT_EXIST, accountName); + return false; + } + + flags = (*result)[0].Get(); + } + + if (!flags) + { + handler->PSendSysMessage(LANG_ACCOUNT_FLAG_LIST_EMPTY, accountName, accountId); + return true; + } + + handler->PSendSysMessage(LANG_ACCOUNT_FLAG_LIST_HEADER, accountName, accountId); + for (uint8 i = 0; i < MAX_ACCOUNT_FLAG; ++i) + if (flags & (uint32(1) << i)) + handler->PSendSysMessage(LANG_SUBCMDS_LIST_ENTRY, accountFlagNames[i].full); + + return true; + } + + static bool ChangeAccountFlag(ChatHandler* handler, AccountIdentifier const& account, std::string_view flagArg, bool add) + { + if (handler->HasLowerSecurityAccount(nullptr, account.GetID(), true)) + return false; + + Optional bit = ParseAccountFlagBit(flagArg); + if (!bit) + { + handler->SendErrorMessage(LANG_ACCOUNT_FLAG_INVALID, flagArg); + return false; + } + + // ACCOUNT_FLAG_GM is handled by GMLevel and should not be allowed to set manually + uint32 const flag = uint32(1) << *bit; + if (flag & ACCOUNT_FLAG_GM) + { + handler->SendErrorMessage(LANG_ACCOUNT_FLAG_RESERVED); + return false; + } + + if (WorldSession* session = sWorldSessionMgr->FindSession(account.GetID())) + session->UpdateAccountFlag(flag, !add); + else + LoginDatabase.Execute("UPDATE account SET Flags = Flags {} {} WHERE id = {}", + add ? "|" : "& ~", flag, account.GetID()); + + handler->PSendSysMessage(add ? LANG_ACCOUNT_FLAG_ADDED : LANG_ACCOUNT_FLAG_REMOVED, + accountFlagNames[*bit].full, account.GetName(), account.GetID()); + return true; + } + + static bool HandleAccountFlagAddCommand(ChatHandler* handler, AccountIdentifier account, std::string_view flagArg) + { + return ChangeAccountFlag(handler, account, flagArg, true); + } + + static bool HandleAccountFlagRemoveCommand(ChatHandler* handler, AccountIdentifier account, std::string_view flagArg) + { + return ChangeAccountFlag(handler, account, flagArg, false); + } + /// Set email for account static bool HandleAccountSetEmailCommand(ChatHandler* handler, AccountIdentifier account, std::string email, std::string emailConfirmation) diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 456628d03..b795ebf19 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -61,49 +61,6 @@ constexpr auto SPELL_STUCK = 7355; constexpr auto SPELL_FREEZE = 9454; -struct AccountFlagText -{ - AccountFlag flag; - std::string text; -}; - -AccountFlagText const accountFlagText[MAX_ACCOUNT_FLAG] = -{ - { ACCOUNT_FLAG_GM, "ACCOUNT_FLAG_GM" }, - { ACCOUNT_FLAG_NOKICK, "ACCOUNT_FLAG_NOKICK" }, - { ACCOUNT_FLAG_COLLECTOR, "ACCOUNT_FLAG_COLLECTOR" }, - { ACCOUNT_FLAG_TRIAL, "ACCOUNT_FLAG_TRIAL" }, - { ACCOUNT_FLAG_CANCELLED, "ACCOUNT_FLAG_CANCELLED" }, - { ACCOUNT_FLAG_IGR, "ACCOUNT_FLAG_IGR" }, - { ACCOUNT_FLAG_WHOLESALER, "ACCOUNT_FLAG_WHOLESALER" }, - { ACCOUNT_FLAG_PRIVILEGED, "ACCOUNT_FLAG_PRIVILEGED" }, - { ACCOUNT_FLAG_EU_FORBID_ELV, "ACCOUNT_FLAG_EU_FORBID_ELV" }, - { ACCOUNT_FLAG_EU_FORBID_BILLING, "ACCOUNT_FLAG_EU_FORBID_BILLING" }, - { ACCOUNT_FLAG_RESTRICTED, "ACCOUNT_FLAG_RESTRICTED" }, - { ACCOUNT_FLAG_REFERRAL, "ACCOUNT_FLAG_REFERRAL" }, - { ACCOUNT_FLAG_BLIZZARD, "ACCOUNT_FLAG_BLIZZARD" }, - { ACCOUNT_FLAG_RECURRING_BILLING, "ACCOUNT_FLAG_RECURRING_BILLING" }, - { ACCOUNT_FLAG_NOELECTUP, "ACCOUNT_FLAG_NOELECTUP" }, - { ACCOUNT_FLAG_KR_CERTIFICATE, "ACCOUNT_FLAG_KR_CERTIFICATE" }, - { ACCOUNT_FLAG_EXPANSION_COLLECTOR, "ACCOUNT_FLAG_EXPANSION_COLLECTOR" }, - { ACCOUNT_FLAG_DISABLE_VOICE, "ACCOUNT_FLAG_DISABLE_VOICE" }, - { ACCOUNT_FLAG_DISABLE_VOICE_SPEAK, "ACCOUNT_FLAG_DISABLE_VOICE_SPEAK" }, - { ACCOUNT_FLAG_REFERRAL_RESURRECT, "ACCOUNT_FLAG_REFERRAL_RESURRECT" }, - { ACCOUNT_FLAG_EU_FORBID_CC, "ACCOUNT_FLAG_EU_FORBID_CC" }, - { ACCOUNT_FLAG_OPENBETA_DELL, "ACCOUNT_FLAG_OPENBETA_DELL" }, - { ACCOUNT_FLAG_PROPASS, "ACCOUNT_FLAG_PROPASS" }, - { ACCOUNT_FLAG_PROPASS_LOCK, "ACCOUNT_FLAG_PROPASS_LOCK" }, - { ACCOUNT_FLAG_PENDING_UPGRADE, "ACCOUNT_FLAG_PENDING_UPGRADE" }, - { ACCOUNT_FLAG_RETAIL_FROM_TRIAL, "ACCOUNT_FLAG_RETAIL_FROM_TRIAL" }, - { ACCOUNT_FLAG_EXPANSION2_COLLECTOR, "ACCOUNT_FLAG_EXPANSION2_COLLECTOR" }, - { ACCOUNT_FLAG_OVERMIND_LINKED, "ACCOUNT_FLAG_OVERMIND_LINKED" }, - { ACCOUNT_FLAG_DEMOS, "ACCOUNT_FLAG_DEMOS" }, - { ACCOUNT_FLAG_DEATH_KNIGHT_OK, "ACCOUNT_FLAG_DEATH_KNIGHT_OK" }, - { ACCOUNT_FLAG_S2_REQUIRE_IGR, "ACCOUNT_FLAG_S2_REQUIRE_IGR" }, - { ACCOUNT_FLAG_S2_TRIAL, "ACCOUNT_FLAG_S2_TRIAL" }, - // { ACCOUNT_FLAG_S2_RESTRICTED, "ACCOUNT_FLAG_S2_RESTRICTED" } -}; - std::string const GetLocalizeCreatureName(Creature* creature, LocaleConstant locale) { auto creatureTemplate = sObjectMgr->GetCreatureTemplate(creature->GetEntry()); @@ -2280,8 +2237,8 @@ public: uint32 accountFlags = playerTarget->GetSession()->GetAccountFlags(); handler->PSendSysMessage(LANG_ACCOUNT_FLAGS_PINFO); for (uint8 i = 0; i < MAX_ACCOUNT_FLAG; i++) - if (accountFlags & static_cast(accountFlagText[i].flag)) - handler->PSendSysMessage(LANG_SUBCMDS_LIST_ENTRY, accountFlagText[i].text); + if (accountFlags & (uint32(1) << i)) + handler->PSendSysMessage(LANG_SUBCMDS_LIST_ENTRY, accountFlagNames[i].full); } // Output VI. LANG_PINFO_ACC_LASTLOGIN