From 657a47b02982e3e3c717b91a33207199691325b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 27 Apr 2022 01:06:15 +0200 Subject: [PATCH] Added flexible ACL roles (#7) Resolve #4 --- core/core.pro | 2 + core/include/acl_roles_handler.h | 210 ++++++++++ core/include/aoclient.h | 391 +++++++++--------- core/include/db_manager.h | 52 +-- core/include/server.h | 5 + core/src/acl_roles_handler.cpp | 283 +++++++++++++ core/src/aoclient.cpp | 52 ++- core/src/commands/area.cpp | 4 +- core/src/commands/authentication.cpp | 132 ++---- core/src/commands/casing.cpp | 2 +- core/src/commands/messaging.cpp | 2 +- core/src/commands/moderation.cpp | 8 +- core/src/commands/music.cpp | 2 +- core/src/commands/roleplay.cpp | 2 +- core/src/db_manager.cpp | 49 +-- core/src/packets.cpp | 12 +- core/src/server.cpp | 16 +- tests/tests.pro | 3 +- .../tst_unittest_acl_roles_handler.cpp | 193 +++++++++ .../unittest_acl_roles_handler.pro | 5 + 20 files changed, 1020 insertions(+), 405 deletions(-) create mode 100644 core/include/acl_roles_handler.h create mode 100644 core/src/acl_roles_handler.cpp create mode 100644 tests/unittest_acl_roles_handler/tst_unittest_acl_roles_handler.cpp create mode 100644 tests/unittest_acl_roles_handler/unittest_acl_roles_handler.pro diff --git a/core/core.pro b/core/core.pro index 650e51a..86f0157 100644 --- a/core/core.pro +++ b/core/core.pro @@ -25,6 +25,7 @@ DESTDIR = $$PWD/../bin #DEFINES += NET_DEBUG SOURCES += \ + src/acl_roles_handler.cpp \ src/aoclient.cpp \ src/aopacket.cpp \ src/area_data.cpp \ @@ -51,6 +52,7 @@ SOURCES += \ src/music_manager.cpp HEADERS += include/aoclient.h \ + include/acl_roles_handler.h \ include/aopacket.h \ include/area_data.h \ include/config_manager.h \ diff --git a/core/include/acl_roles_handler.h b/core/include/acl_roles_handler.h new file mode 100644 index 0000000..c6c5dec --- /dev/null +++ b/core/include/acl_roles_handler.h @@ -0,0 +1,210 @@ +#ifndef ACL_ROLES_HANDLER_H +#define ACL_ROLES_HANDLER_H + +#include +#include +#include + +class ACLRole +{ + public: + /** + * @brief This enum is used to specify permissions of a role. + */ + enum Permission : unsigned int + { + NONE = 0, + KICK = 1 << 0, + BAN = 1 << 1, + BGLOCK = 1 << 2, + MODIFY_USERS = 1 << 3, + CM = 1 << 4, + GLOBAL_TIMER = 1 << 5, + EVI_MOD = 1 << 6, + MOTD = 1 << 7, + ANNOUNCE = 1 << 8, + MODCHAT = 1 << 9, + MUTE = 1 << 10, + UNCM = 1 << 11, + SAVETEST = 1 << 12, + FORCE_CHARSELECT = 1 << 13, + BYPASS_LOCKS = 1 << 14, + IGNORE_BGLIST = 1 << 15, + SEND_NOTICE = 1 << 16, + JUKEBOX = 1 << 17, + SUPER = 0xffffffff, + }; + Q_DECLARE_FLAGS(Permissions, Permission); + + /** + * @brief Shared read-only captions for each permissions. + * + * @see ACLRoleHandler#loadFile and ACLRoleHandler#saveFile + */ + static const QHash permission_captions; + + /** + * @brief Constructs a role without any permissions. + */ + ACLRole(); + + /** + * @brief Constructs a role of the given permissions. + * + * @param f_permissions The permissions to which + */ + ACLRole(ACLRole::Permissions f_permissions); + + /** + * @brief Destroys the role. + */ + ~ACLRole(); + + /** + * @brief Returns the permission flags for this role. + * + * @return Permission flags. + */ + ACLRole::Permissions getPermissions() const; + + /** + * @brief Checks if a given permission is set. + * + * @param f_permission The permission flag to check. + * + * @return True if the permission is set, false otherwise. + */ + bool checkPermission(ACLRole::Permission f_permission) const; + + /** + * @brief Sets the permission if f_mode is true or unsets if f_mode is false. + * + * @param f_permission The permission flag to set. + * + * @param f_mode If true, will set the flag, unsets otherwise. + */ + void setPermission(ACLRole::Permission f_permission, bool f_mode); + + /** + * @brief Sets the permission flags to the given permission flags. + * + * @param f_permissions The permission flags to set to. + */ + void setPermissions(ACLRole::Permissions f_permissions); + + private: + /** + * @brief The permission flags of the role. + */ + ACLRole::Permissions m_permissions; +}; + +class ACLRolesHandler : public QObject +{ + Q_OBJECT + + public: + /** + * @brief The identifier of the NONE role. + */ + static const QString NONE_ID; + + /** + * @brief The identifier of the SUPER role. + */ + static const QString SUPER_ID; + + /** + * @brief Constructs a role handler with parent object. + * + * @details The role handler starts off empty without any roles known with the exception of the shared read-only roles. + * + * The role handler do not load or save roles automatically. + * + * Multiple role handlers can exist simultaneously with different roles and these same roles may have entirely different permissions. + * + * @param parent Qt-based parent. + */ + ACLRolesHandler(QObject *parent = nullptr); + + /** + * Destroys the role handler. + */ + ~ACLRolesHandler(); + + /** + * @brief Checks if a role with the given identifier exists. + * + * @param f_id The identifier of the role. The identifier is not case-sensitive. + * + * @return True if the role exists, false otherwise. + */ + bool roleExists(QString f_id); + + /** + * @brief Returns a role with the given identifier. If the role does not exist, a default constructed role will be returned. + * + * @param f_id The identifier of the role. The identifier is not case-sensitive. + * + * @return A role. + */ + ACLRole getRoleById(QString f_id); + + /** + * @brief Inserts a role with the given identifier. If a role already exists, it will be overwritten. Read-only roles cannot be replaced and will return false. + * + * @param f_id The identifier of the role. The identifier is not case-sensitive. + * + * @param f_role The role to insert. + * + * @return True if the role was inserted, false otherwise. + */ + bool insertRole(QString f_id, ACLRole f_role); + + /** + * @brief Removes a role of the given identifier. Read-only roles cannot be removed and will return false. + * + * @param f_id The identifier of the role. The identifier is not case-sensitive. + * + * @return True if the role was removed, false otherwise. + */ + bool removeRole(QString f_id); + + /** + * @brief Removes all roles. + */ + void clearRoles(); + + /** + * @brief Clear the current roles and load the roles from the given file. The file must be INI format compatible. + * + * @param f_filename The filename may have no path, a relative path or an absolute path. + * + * @return True if successfull, false otherwise. + */ + bool loadFile(QString f_filename); + + /** + * @brief Save the current roles to the given file. The file is saved to the INI format. + * + * @warning This will completely overwrite the given file. + * + * @param f_filename The filename may have no path, a relative path or an absolute path. + * + * @return True if successfull, false otherwise. + */ + bool saveFile(QString f_filename); + + private: + /** + * @brief Shared read-only standard roles with the appropriate permissions. + */ + static const QHash readonly_roles; + + /** + * @brief The roles of the handler. + */ + QHash m_roles; +}; + +#endif // ACL_ROLES_HANDLER_H diff --git a/core/include/aoclient.h b/core/include/aoclient.h index f9245d4..5e95c78 100644 --- a/core/include/aoclient.h +++ b/core/include/aoclient.h @@ -24,12 +24,13 @@ #include #include #include -#include #include +#include #if QT_VERSION > QT_VERSION_CHECK(5, 10, 0) #include #endif +#include "include/acl_roles_handler.h" #include "include/aopacket.h" class AreaData; @@ -87,6 +88,13 @@ class AOClient : public QObject */ bool hasJoined() const; + /** + * @brief Returns true if the client has logged-in as a role. + * + * @return True if loggged-in, false otherwise. + */ + bool isAuthenticated() const; + /** * @brief Calculates the client's IPID based on a hashed version of its IP. */ @@ -146,11 +154,6 @@ class AOClient : public QObject */ QString m_current_iniswap; - /** - * @brief If true, the client is a logged-in moderator. - */ - bool m_authenticated = false; - /** * @brief If using advanced authentication, this is the moderator name that the client has logged in with. */ @@ -221,33 +224,6 @@ class AOClient : public QObject */ ClientVersion m_version; - /** - * @brief The authorisation bitflag, representing what permissions a client can have. - * - * @showinitializer - */ - QMap ACLFlags{ - {"NONE", 0ULL}, - {"KICK", 1ULL << 0}, - {"BAN", 1ULL << 1}, - {"BGLOCK", 1ULL << 2}, - {"MODIFY_USERS", 1ULL << 3}, - {"CM", 1ULL << 4}, - {"GLOBAL_TIMER", 1ULL << 5}, - {"EVI_MOD", 1ULL << 6}, - {"MOTD", 1ULL << 7}, - {"ANNOUNCE", 1ULL << 8}, - {"MODCHAT", 1ULL << 9}, - {"MUTE", 1ULL << 10}, - {"UNCM", 1ULL << 11}, - {"SAVETEST", 1ULL << 12}, - {"FORCE_CHARSELECT", 1ULL << 13}, - {"BYPASS_LOCKS", 1ULL << 14}, - {"IGNORE_BGLIST", 1ULL << 15}, - {"SEND_NOTICE", 1ULL << 16}, - {"JUKEBOX", 1ULL << 17}, - {"SUPER", ~0ULL}}; - /** * @brief A list of 5 casing preferences (def, pro, judge, jury, steno) */ @@ -310,14 +286,13 @@ class AOClient : public QObject bool m_is_logging_in = false; /** - * @brief Checks if the client would be authorised to something based on its necessary permissions. + * @brief Checks if the client's ACL role has permission for the given permission. * - * @param acl_mask The permissions bitflag that the client's own permissions should be checked against. + * @param f_permission The permission flags. * - * @return True if the client's permissions are high enough for `acl_mask`, or higher than it. - * False if the client is missing some permissions. + * @return True if the client has permission, false otherwise. */ - bool checkAuth(unsigned long long acl_mask); + bool checkPermission(ACLRole::Permission f_permission) const; public slots: /** @@ -611,6 +586,16 @@ class AOClient : public QObject */ ///@{ + /** + * @brief If true, the client is a logged-in moderator. + */ + bool m_authenticated = false; + + /** + * @brief The ACL role identifier, used to determine what ACL role the client is linked to. + */ + QString m_acl_role_id; + /** * @brief The client's character ID. * @@ -660,8 +645,8 @@ class AOClient : public QObject /// Describes a packet's interpretation details. struct PacketInfo { - unsigned long long acl_mask; //!< The permissions necessary for the packet. - int minArgs; //!< The minimum arguments needed for the packet to be interpreted correctly / make sense. + ACLRole::Permission acl_permission; //!< The permissions necessary for the packet. + int minArgs; //!< The minimum arguments needed for the packet to be interpreted correctly / make sense. void (AOClient::*action)(AreaData *, int, QStringList, AOPacket); }; @@ -686,27 +671,27 @@ class AOClient : public QObject * See @ref PacketInfo "the type's documentation" for more details. */ const QMap packets{ - {"HI", {ACLFlags.value("NONE"), 1, &AOClient::pktHardwareId}}, - {"ID", {ACLFlags.value("NONE"), 2, &AOClient::pktSoftwareId}}, - {"askchaa", {ACLFlags.value("NONE"), 0, &AOClient::pktBeginLoad}}, - {"RC", {ACLFlags.value("NONE"), 0, &AOClient::pktRequestChars}}, - {"RM", {ACLFlags.value("NONE"), 0, &AOClient::pktRequestMusic}}, - {"RD", {ACLFlags.value("NONE"), 0, &AOClient::pktLoadingDone}}, - {"PW", {ACLFlags.value("NONE"), 1, &AOClient::pktCharPassword}}, - {"CC", {ACLFlags.value("NONE"), 3, &AOClient::pktSelectChar}}, - {"MS", {ACLFlags.value("NONE"), 15, &AOClient::pktIcChat}}, - {"CT", {ACLFlags.value("NONE"), 2, &AOClient::pktOocChat}}, - {"CH", {ACLFlags.value("NONE"), 1, &AOClient::pktPing}}, - {"MC", {ACLFlags.value("NONE"), 2, &AOClient::pktChangeMusic}}, - {"RT", {ACLFlags.value("NONE"), 1, &AOClient::pktWtCe}}, - {"HP", {ACLFlags.value("NONE"), 2, &AOClient::pktHpBar}}, - {"WSIP", {ACLFlags.value("NONE"), 1, &AOClient::pktWebSocketIp}}, - {"ZZ", {ACLFlags.value("NONE"), 0, &AOClient::pktModCall}}, - {"PE", {ACLFlags.value("NONE"), 3, &AOClient::pktAddEvidence}}, - {"DE", {ACLFlags.value("NONE"), 1, &AOClient::pktRemoveEvidence}}, - {"EE", {ACLFlags.value("NONE"), 4, &AOClient::pktEditEvidence}}, - {"SETCASE", {ACLFlags.value("NONE"), 7, &AOClient::pktSetCase}}, - {"CASEA", {ACLFlags.value("NONE"), 6, &AOClient::pktAnnounceCase}}, + {"HI", {ACLRole::NONE, 1, &AOClient::pktHardwareId}}, + {"ID", {ACLRole::NONE, 2, &AOClient::pktSoftwareId}}, + {"askchaa", {ACLRole::NONE, 0, &AOClient::pktBeginLoad}}, + {"RC", {ACLRole::NONE, 0, &AOClient::pktRequestChars}}, + {"RM", {ACLRole::NONE, 0, &AOClient::pktRequestMusic}}, + {"RD", {ACLRole::NONE, 0, &AOClient::pktLoadingDone}}, + {"PW", {ACLRole::NONE, 1, &AOClient::pktCharPassword}}, + {"CC", {ACLRole::NONE, 3, &AOClient::pktSelectChar}}, + {"MS", {ACLRole::NONE, 15, &AOClient::pktIcChat}}, + {"CT", {ACLRole::NONE, 2, &AOClient::pktOocChat}}, + {"CH", {ACLRole::NONE, 1, &AOClient::pktPing}}, + {"MC", {ACLRole::NONE, 2, &AOClient::pktChangeMusic}}, + {"RT", {ACLRole::NONE, 1, &AOClient::pktWtCe}}, + {"HP", {ACLRole::NONE, 2, &AOClient::pktHpBar}}, + {"WSIP", {ACLRole::NONE, 1, &AOClient::pktWebSocketIp}}, + {"ZZ", {ACLRole::NONE, 0, &AOClient::pktModCall}}, + {"PE", {ACLRole::NONE, 3, &AOClient::pktAddEvidence}}, + {"DE", {ACLRole::NONE, 1, &AOClient::pktRemoveEvidence}}, + {"EE", {ACLRole::NONE, 4, &AOClient::pktEditEvidence}}, + {"SETCASE", {ACLRole::NONE, 7, &AOClient::pktSetCase}}, + {"CASEA", {ACLRole::NONE, 6, &AOClient::pktAnnounceCase}}, }; /** @@ -779,7 +764,7 @@ class AOClient : public QObject * * @iscommand */ - void cmdAddPerms(int argc, QStringList argv); + void cmdSetPerms(int argc, QStringList argv); /** * @brief Removes permissions from a given user. @@ -2062,8 +2047,8 @@ class AOClient : public QObject */ struct CommandInfo { - unsigned long long acl_mask; //!< The permissions necessary to be able to run the command. @see ACLFlags. - int minArgs; //!< The minimum mandatory arguments needed for the command to function. + ACLRole::Permission acl_permission; //!< The permissions necessary to be able to run the command. @see ACLRole::Permission. + int minArgs; //!< The minimum mandatory arguments needed for the command to function. void (AOClient::*action)(int, QStringList); }; @@ -2087,145 +2072,145 @@ class AOClient : public QObject * See @ref CommandInfo "the type's documentation" for more details. */ const QMap commands{ - {"login", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogin}}, - {"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}}, - {"gas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}}, - {"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}}, - {"ga", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}}, - {"ban", {ACLFlags.value("BAN"), 3, &AOClient::cmdBan}}, - {"kick", {ACLFlags.value("KICK"), 2, &AOClient::cmdKick}}, - {"changeauth", {ACLFlags.value("SUPER"), 0, &AOClient::cmdChangeAuth}}, - {"rootpass", {ACLFlags.value("SUPER"), 1, &AOClient::cmdSetRootPass}}, - {"background", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground}}, - {"bg", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground}}, - {"bglock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgLock}}, - {"bgunlock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgUnlock}}, - {"adduser", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddUser}}, - {"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms}}, - {"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms}}, - {"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms}}, - {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}}, - {"logout", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogout}}, - {"pos", {ACLFlags.value("NONE"), 1, &AOClient::cmdPos}}, - {"g", {ACLFlags.value("NONE"), 1, &AOClient::cmdG}}, - {"need", {ACLFlags.value("NONE"), 1, &AOClient::cmdNeed}}, - {"coinflip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip}}, - {"roll", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll}}, - {"r", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll}}, - {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP}}, - {"doc", {ACLFlags.value("NONE"), 0, &AOClient::cmdDoc}}, - {"cleardoc", {ACLFlags.value("NONE"), 0, &AOClient::cmdClearDoc}}, - {"cm", {ACLFlags.value("NONE"), 0, &AOClient::cmdCM}}, - {"uncm", {ACLFlags.value("CM"), 0, &AOClient::cmdUnCM}}, - {"invite", {ACLFlags.value("CM"), 1, &AOClient::cmdInvite}}, - {"uninvite", {ACLFlags.value("CM"), 1, &AOClient::cmdUnInvite}}, - {"lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, - {"area_lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, - {"spectatable", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, - {"area_spectate", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, - {"unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, - {"area_unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, - {"timer", {ACLFlags.value("CM"), 0, &AOClient::cmdTimer}}, - {"area", {ACLFlags.value("NONE"), 1, &AOClient::cmdArea}}, - {"play", {ACLFlags.value("CM"), 1, &AOClient::cmdPlay}}, - {"areakick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, - {"area_kick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, - {"randomchar", {ACLFlags.value("NONE"), 0, &AOClient::cmdRandomChar}}, - {"switch", {ACLFlags.value("NONE"), 1, &AOClient::cmdSwitch}}, - {"toggleglobal", {ACLFlags.value("NONE"), 0, &AOClient::cmdToggleGlobal}}, - {"mods", {ACLFlags.value("NONE"), 0, &AOClient::cmdMods}}, - {"commands", {ACLFlags.value("NONE"), 0, &AOClient::cmdCommands}}, - {"status", {ACLFlags.value("NONE"), 1, &AOClient::cmdStatus}}, - {"forcepos", {ACLFlags.value("CM"), 2, &AOClient::cmdForcePos}}, - {"currentmusic", {ACLFlags.value("NONE"), 0, &AOClient::cmdCurrentMusic}}, - {"pm", {ACLFlags.value("NONE"), 2, &AOClient::cmdPM}}, - {"evidence_mod", {ACLFlags.value("EVI_MOD"), 1, &AOClient::cmdEvidenceMod}}, - {"motd", {ACLFlags.value("NONE"), 0, &AOClient::cmdMOTD}}, - {"announce", {ACLFlags.value("ANNOUNCE"), 1, &AOClient::cmdAnnounce}}, - {"m", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdM}}, - {"gm", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdGM}}, - {"mute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdMute}}, - {"unmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnMute}}, - {"bans", {ACLFlags.value("BAN"), 0, &AOClient::cmdBans}}, - {"unban", {ACLFlags.value("BAN"), 1, &AOClient::cmdUnBan}}, - {"removeuser", {ACLFlags.value("MODIFY_USERS"), 1, &AOClient::cmdRemoveUser}}, - {"subtheme", {ACLFlags.value("CM"), 1, &AOClient::cmdSubTheme}}, - {"about", {ACLFlags.value("NONE"), 0, &AOClient::cmdAbout}}, - {"evidence_swap", {ACLFlags.value("CM"), 2, &AOClient::cmdEvidence_Swap}}, - {"notecard", {ACLFlags.value("NONE"), 1, &AOClient::cmdNoteCard}}, - {"notecardreveal", {ACLFlags.value("CM"), 0, &AOClient::cmdNoteCardReveal}}, - {"notecard_reveal", {ACLFlags.value("CM"), 0, &AOClient::cmdNoteCardReveal}}, - {"notecardclear", {ACLFlags.value("NONE"), 0, &AOClient::cmdNoteCardClear}}, - {"notecard_clear", {ACLFlags.value("NONE"), 0, &AOClient::cmdNoteCardClear}}, - {"8ball", {ACLFlags.value("NONE"), 1, &AOClient::cmd8Ball}}, - {"lm", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdLM}}, - {"judgelog", {ACLFlags.value("CM"), 0, &AOClient::cmdJudgeLog}}, - {"allowblankposting", {ACLFlags.value("MODCHAT"), 0, &AOClient::cmdAllowBlankposting}}, - {"allow_blankposting", {ACLFlags.value("MODCHAT"), 0, &AOClient::cmdAllowBlankposting}}, - {"gimp", {ACLFlags.value("MUTE"), 1, &AOClient::cmdGimp}}, - {"ungimp", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnGimp}}, - {"baninfo", {ACLFlags.value("BAN"), 1, &AOClient::cmdBanInfo}}, - {"testify", {ACLFlags.value("CM"), 0, &AOClient::cmdTestify}}, - {"testimony", {ACLFlags.value("NONE"), 0, &AOClient::cmdTestimony}}, - {"examine", {ACLFlags.value("CM"), 0, &AOClient::cmdExamine}}, - {"pause", {ACLFlags.value("CM"), 0, &AOClient::cmdPauseTestimony}}, - {"delete", {ACLFlags.value("CM"), 0, &AOClient::cmdDeleteStatement}}, - {"update", {ACLFlags.value("CM"), 0, &AOClient::cmdUpdateStatement}}, - {"add", {ACLFlags.value("CM"), 0, &AOClient::cmdAddStatement}}, - {"reload", {ACLFlags.value("SUPER"), 0, &AOClient::cmdReload}}, - {"disemvowel", {ACLFlags.value("MUTE"), 1, &AOClient::cmdDisemvowel}}, - {"undisemvowel", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnDisemvowel}}, - {"shake", {ACLFlags.value("MUTE"), 1, &AOClient::cmdShake}}, - {"unshake", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnShake}}, - {"forceimmediate", {ACLFlags.value("CM"), 0, &AOClient::cmdForceImmediate}}, - {"force_noint_pres", {ACLFlags.value("CM"), 0, &AOClient::cmdForceImmediate}}, - {"allowiniswap", {ACLFlags.value("CM"), 0, &AOClient::cmdAllowIniswap}}, - {"allow_iniswap", {ACLFlags.value("CM"), 0, &AOClient::cmdAllowIniswap}}, - {"afk", {ACLFlags.value("NONE"), 0, &AOClient::cmdAfk}}, - {"savetestimony", {ACLFlags.value("NONE"), 1, &AOClient::cmdSaveTestimony}}, - {"loadtestimony", {ACLFlags.value("CM"), 1, &AOClient::cmdLoadTestimony}}, - {"permitsaving", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdPermitSaving}}, - {"mutepm", {ACLFlags.value("NONE"), 0, &AOClient::cmdMutePM}}, - {"toggleadverts", {ACLFlags.value("NONE"), 0, &AOClient::cmdToggleAdverts}}, - {"oocmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdOocMute}}, - {"ooc_mute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdOocMute}}, - {"oocunmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdOocUnMute}}, - {"ooc_unmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdOocUnMute}}, - {"blockwtce", {ACLFlags.value("MUTE"), 1, &AOClient::cmdBlockWtce}}, - {"block_wtce", {ACLFlags.value("MUTE"), 1, &AOClient::cmdBlockWtce}}, - {"unblockwtce", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnBlockWtce}}, - {"unblock_wtce", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnBlockWtce}}, - {"blockdj", {ACLFlags.value("MUTE"), 1, &AOClient::cmdBlockDj}}, - {"block_dj", {ACLFlags.value("MUTE"), 1, &AOClient::cmdBlockDj}}, - {"unblockdj", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnBlockDj}}, - {"unblock_dj", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnBlockDj}}, - {"charcurse", {ACLFlags.value("MUTE"), 1, &AOClient::cmdCharCurse}}, - {"uncharcurse", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnCharCurse}}, - {"charselect", {ACLFlags.value("NONE"), 0, &AOClient::cmdCharSelect}}, - {"togglemusic", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleMusic}}, - {"a", {ACLFlags.value("NONE"), 2, &AOClient::cmdA}}, - {"s", {ACLFlags.value("NONE"), 0, &AOClient::cmdS}}, - {"kickuid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}}, - {"kick_uid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}}, - {"firstperson", {ACLFlags.value("NONE"), 0, &AOClient::cmdFirstPerson}}, - {"updateban", {ACLFlags.value("BAN"), 3, &AOClient::cmdUpdateBan}}, - {"update_ban", {ACLFlags.value("BAN"), 3, &AOClient::cmdUpdateBan}}, - {"changepass", {ACLFlags.value("NONE"), 1, &AOClient::cmdChangePassword}}, - {"ignorebglist", {ACLFlags.value("IGNORE_BGLIST"), 0, &AOClient::cmdIgnoreBgList}}, - {"ignore_bglist", {ACLFlags.value("IGNORE_BGLIST"), 0, &AOClient::cmdIgnoreBgList}}, - {"notice", {ACLFlags.value("SEND_NOTICE"), 1, &AOClient::cmdNotice}}, - {"noticeg", {ACLFlags.value("SEND_NOTICE"), 1, &AOClient::cmdNoticeGlobal}}, - {"togglejukebox", {ACLFlags.value("None"), 0, &AOClient::cmdToggleJukebox}}, - {"help", {ACLFlags.value("NONE"), 1, &AOClient::cmdHelp}}, - {"clearcm", {ACLFlags.value("KICK"), 0, &AOClient::cmdClearCM}}, - {"togglemessage", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleAreaMessageOnJoin}}, - {"clearmessage", {ACLFlags.value("CM"), 0, &AOClient::cmdClearAreaMessage}}, - {"areamessage", {ACLFlags.value("CM"), 0, &AOClient::cmdAreaMessage}}, - {"addsong", {ACLFlags.value("CM"), 1, &AOClient::cmdAddSong}}, - {"addcategory", {ACLFlags.value("CM"), 1, &AOClient::cmdAddCategory}}, - {"removeentry", {ACLFlags.value("CM"), 1, &AOClient::cmdRemoveCategorySong}}, - {"toggleroot", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleRootlist}}, - {"clearcustom", {ACLFlags.value("CM"), 0, &AOClient::cmdClearCustom}}}; + {"login", {ACLRole::NONE, 0, &AOClient::cmdLogin}}, + {"getareas", {ACLRole::NONE, 0, &AOClient::cmdGetAreas}}, + {"gas", {ACLRole::NONE, 0, &AOClient::cmdGetAreas}}, + {"getarea", {ACLRole::NONE, 0, &AOClient::cmdGetArea}}, + {"ga", {ACLRole::NONE, 0, &AOClient::cmdGetArea}}, + {"ban", {ACLRole::BAN, 3, &AOClient::cmdBan}}, + {"kick", {ACLRole::KICK, 2, &AOClient::cmdKick}}, + {"changeauth", {ACLRole::SUPER, 0, &AOClient::cmdChangeAuth}}, + {"rootpass", {ACLRole::SUPER, 1, &AOClient::cmdSetRootPass}}, + {"background", {ACLRole::NONE, 1, &AOClient::cmdSetBackground}}, + {"bg", {ACLRole::NONE, 1, &AOClient::cmdSetBackground}}, + {"bglock", {ACLRole::BGLOCK, 0, &AOClient::cmdBgLock}}, + {"bgunlock", {ACLRole::BGLOCK, 0, &AOClient::cmdBgUnlock}}, + {"adduser", {ACLRole::MODIFY_USERS, 2, &AOClient::cmdAddUser}}, + {"removeuser", {ACLRole::MODIFY_USERS, 1, &AOClient::cmdRemoveUser}}, + {"listusers", {ACLRole::MODIFY_USERS, 0, &AOClient::cmdListUsers}}, + {"setperms", {ACLRole::MODIFY_USERS, 2, &AOClient::cmdSetPerms}}, + {"removeperms", {ACLRole::MODIFY_USERS, 1, &AOClient::cmdRemovePerms}}, + {"listperms", {ACLRole::NONE, 0, &AOClient::cmdListPerms}}, + {"logout", {ACLRole::NONE, 0, &AOClient::cmdLogout}}, + {"pos", {ACLRole::NONE, 1, &AOClient::cmdPos}}, + {"g", {ACLRole::NONE, 1, &AOClient::cmdG}}, + {"need", {ACLRole::NONE, 1, &AOClient::cmdNeed}}, + {"coinflip", {ACLRole::NONE, 0, &AOClient::cmdFlip}}, + {"roll", {ACLRole::NONE, 0, &AOClient::cmdRoll}}, + {"r", {ACLRole::NONE, 0, &AOClient::cmdRoll}}, + {"rollp", {ACLRole::NONE, 0, &AOClient::cmdRollP}}, + {"doc", {ACLRole::NONE, 0, &AOClient::cmdDoc}}, + {"cleardoc", {ACLRole::NONE, 0, &AOClient::cmdClearDoc}}, + {"cm", {ACLRole::NONE, 0, &AOClient::cmdCM}}, + {"uncm", {ACLRole::CM, 0, &AOClient::cmdUnCM}}, + {"invite", {ACLRole::CM, 1, &AOClient::cmdInvite}}, + {"uninvite", {ACLRole::CM, 1, &AOClient::cmdUnInvite}}, + {"lock", {ACLRole::CM, 0, &AOClient::cmdLock}}, + {"area_lock", {ACLRole::CM, 0, &AOClient::cmdLock}}, + {"spectatable", {ACLRole::CM, 0, &AOClient::cmdSpectatable}}, + {"area_spectate", {ACLRole::CM, 0, &AOClient::cmdSpectatable}}, + {"unlock", {ACLRole::CM, 0, &AOClient::cmdUnLock}}, + {"area_unlock", {ACLRole::CM, 0, &AOClient::cmdUnLock}}, + {"timer", {ACLRole::CM, 0, &AOClient::cmdTimer}}, + {"area", {ACLRole::NONE, 1, &AOClient::cmdArea}}, + {"play", {ACLRole::CM, 1, &AOClient::cmdPlay}}, + {"areakick", {ACLRole::CM, 1, &AOClient::cmdAreaKick}}, + {"area_kick", {ACLRole::CM, 1, &AOClient::cmdAreaKick}}, + {"randomchar", {ACLRole::NONE, 0, &AOClient::cmdRandomChar}}, + {"switch", {ACLRole::NONE, 1, &AOClient::cmdSwitch}}, + {"toggleglobal", {ACLRole::NONE, 0, &AOClient::cmdToggleGlobal}}, + {"mods", {ACLRole::NONE, 0, &AOClient::cmdMods}}, + {"commands", {ACLRole::NONE, 0, &AOClient::cmdCommands}}, + {"status", {ACLRole::NONE, 1, &AOClient::cmdStatus}}, + {"forcepos", {ACLRole::CM, 2, &AOClient::cmdForcePos}}, + {"currentmusic", {ACLRole::NONE, 0, &AOClient::cmdCurrentMusic}}, + {"pm", {ACLRole::NONE, 2, &AOClient::cmdPM}}, + {"evidence_mod", {ACLRole::EVI_MOD, 1, &AOClient::cmdEvidenceMod}}, + {"motd", {ACLRole::NONE, 0, &AOClient::cmdMOTD}}, + {"announce", {ACLRole::ANNOUNCE, 1, &AOClient::cmdAnnounce}}, + {"m", {ACLRole::MODCHAT, 1, &AOClient::cmdM}}, + {"gm", {ACLRole::MODCHAT, 1, &AOClient::cmdGM}}, + {"mute", {ACLRole::MUTE, 1, &AOClient::cmdMute}}, + {"unmute", {ACLRole::MUTE, 1, &AOClient::cmdUnMute}}, + {"bans", {ACLRole::BAN, 0, &AOClient::cmdBans}}, + {"unban", {ACLRole::BAN, 1, &AOClient::cmdUnBan}}, + {"subtheme", {ACLRole::CM, 1, &AOClient::cmdSubTheme}}, + {"about", {ACLRole::NONE, 0, &AOClient::cmdAbout}}, + {"evidence_swap", {ACLRole::CM, 2, &AOClient::cmdEvidence_Swap}}, + {"notecard", {ACLRole::NONE, 1, &AOClient::cmdNoteCard}}, + {"notecardreveal", {ACLRole::CM, 0, &AOClient::cmdNoteCardReveal}}, + {"notecard_reveal", {ACLRole::CM, 0, &AOClient::cmdNoteCardReveal}}, + {"notecardclear", {ACLRole::NONE, 0, &AOClient::cmdNoteCardClear}}, + {"notecard_clear", {ACLRole::NONE, 0, &AOClient::cmdNoteCardClear}}, + {"8ball", {ACLRole::NONE, 1, &AOClient::cmd8Ball}}, + {"lm", {ACLRole::MODCHAT, 1, &AOClient::cmdLM}}, + {"judgelog", {ACLRole::CM, 0, &AOClient::cmdJudgeLog}}, + {"allowblankposting", {ACLRole::MODCHAT, 0, &AOClient::cmdAllowBlankposting}}, + {"allow_blankposting", {ACLRole::MODCHAT, 0, &AOClient::cmdAllowBlankposting}}, + {"gimp", {ACLRole::MUTE, 1, &AOClient::cmdGimp}}, + {"ungimp", {ACLRole::MUTE, 1, &AOClient::cmdUnGimp}}, + {"baninfo", {ACLRole::BAN, 1, &AOClient::cmdBanInfo}}, + {"testify", {ACLRole::CM, 0, &AOClient::cmdTestify}}, + {"testimony", {ACLRole::NONE, 0, &AOClient::cmdTestimony}}, + {"examine", {ACLRole::CM, 0, &AOClient::cmdExamine}}, + {"pause", {ACLRole::CM, 0, &AOClient::cmdPauseTestimony}}, + {"delete", {ACLRole::CM, 0, &AOClient::cmdDeleteStatement}}, + {"update", {ACLRole::CM, 0, &AOClient::cmdUpdateStatement}}, + {"add", {ACLRole::CM, 0, &AOClient::cmdAddStatement}}, + {"reload", {ACLRole::SUPER, 0, &AOClient::cmdReload}}, + {"disemvowel", {ACLRole::MUTE, 1, &AOClient::cmdDisemvowel}}, + {"undisemvowel", {ACLRole::MUTE, 1, &AOClient::cmdUnDisemvowel}}, + {"shake", {ACLRole::MUTE, 1, &AOClient::cmdShake}}, + {"unshake", {ACLRole::MUTE, 1, &AOClient::cmdUnShake}}, + {"forceimmediate", {ACLRole::CM, 0, &AOClient::cmdForceImmediate}}, + {"force_noint_pres", {ACLRole::CM, 0, &AOClient::cmdForceImmediate}}, + {"allowiniswap", {ACLRole::CM, 0, &AOClient::cmdAllowIniswap}}, + {"allow_iniswap", {ACLRole::CM, 0, &AOClient::cmdAllowIniswap}}, + {"afk", {ACLRole::NONE, 0, &AOClient::cmdAfk}}, + {"savetestimony", {ACLRole::NONE, 1, &AOClient::cmdSaveTestimony}}, + {"loadtestimony", {ACLRole::CM, 1, &AOClient::cmdLoadTestimony}}, + {"permitsaving", {ACLRole::MODCHAT, 1, &AOClient::cmdPermitSaving}}, + {"mutepm", {ACLRole::NONE, 0, &AOClient::cmdMutePM}}, + {"toggleadverts", {ACLRole::NONE, 0, &AOClient::cmdToggleAdverts}}, + {"oocmute", {ACLRole::MUTE, 1, &AOClient::cmdOocMute}}, + {"ooc_mute", {ACLRole::MUTE, 1, &AOClient::cmdOocMute}}, + {"oocunmute", {ACLRole::MUTE, 1, &AOClient::cmdOocUnMute}}, + {"ooc_unmute", {ACLRole::MUTE, 1, &AOClient::cmdOocUnMute}}, + {"blockwtce", {ACLRole::MUTE, 1, &AOClient::cmdBlockWtce}}, + {"block_wtce", {ACLRole::MUTE, 1, &AOClient::cmdBlockWtce}}, + {"unblockwtce", {ACLRole::MUTE, 1, &AOClient::cmdUnBlockWtce}}, + {"unblock_wtce", {ACLRole::MUTE, 1, &AOClient::cmdUnBlockWtce}}, + {"blockdj", {ACLRole::MUTE, 1, &AOClient::cmdBlockDj}}, + {"block_dj", {ACLRole::MUTE, 1, &AOClient::cmdBlockDj}}, + {"unblockdj", {ACLRole::MUTE, 1, &AOClient::cmdUnBlockDj}}, + {"unblock_dj", {ACLRole::MUTE, 1, &AOClient::cmdUnBlockDj}}, + {"charcurse", {ACLRole::MUTE, 1, &AOClient::cmdCharCurse}}, + {"uncharcurse", {ACLRole::MUTE, 1, &AOClient::cmdUnCharCurse}}, + {"charselect", {ACLRole::NONE, 0, &AOClient::cmdCharSelect}}, + {"togglemusic", {ACLRole::CM, 0, &AOClient::cmdToggleMusic}}, + {"a", {ACLRole::NONE, 2, &AOClient::cmdA}}, + {"s", {ACLRole::NONE, 0, &AOClient::cmdS}}, + {"kickuid", {ACLRole::KICK, 2, &AOClient::cmdKickUid}}, + {"kick_uid", {ACLRole::KICK, 2, &AOClient::cmdKickUid}}, + {"firstperson", {ACLRole::NONE, 0, &AOClient::cmdFirstPerson}}, + {"updateban", {ACLRole::BAN, 3, &AOClient::cmdUpdateBan}}, + {"update_ban", {ACLRole::BAN, 3, &AOClient::cmdUpdateBan}}, + {"changepass", {ACLRole::NONE, 1, &AOClient::cmdChangePassword}}, + {"ignorebglist", {ACLRole::IGNORE_BGLIST, 0, &AOClient::cmdIgnoreBgList}}, + {"ignore_bglist", {ACLRole::IGNORE_BGLIST, 0, &AOClient::cmdIgnoreBgList}}, + {"notice", {ACLRole::SEND_NOTICE, 1, &AOClient::cmdNotice}}, + {"noticeg", {ACLRole::SEND_NOTICE, 1, &AOClient::cmdNoticeGlobal}}, + {"togglejukebox", {ACLRole::NONE, 0, &AOClient::cmdToggleJukebox}}, + {"help", {ACLRole::NONE, 1, &AOClient::cmdHelp}}, + {"clearcm", {ACLRole::KICK, 0, &AOClient::cmdClearCM}}, + {"togglemessage", {ACLRole::CM, 0, &AOClient::cmdToggleAreaMessageOnJoin}}, + {"clearmessage", {ACLRole::CM, 0, &AOClient::cmdClearAreaMessage}}, + {"areamessage", {ACLRole::CM, 0, &AOClient::cmdAreaMessage}}, + {"addsong", {ACLRole::CM, 1, &AOClient::cmdAddSong}}, + {"addcategory", {ACLRole::CM, 1, &AOClient::cmdAddCategory}}, + {"removeentry", {ACLRole::CM, 1, &AOClient::cmdRemoveCategorySong}}, + {"toggleroot", {ACLRole::CM, 0, &AOClient::cmdToggleRootlist}}, + {"clearcustom", {ACLRole::CM, 0, &AOClient::cmdClearCustom}}}; /** * @brief Filled with part of a packet if said packet could not be read fully from the client's socket. diff --git a/core/include/db_manager.h b/core/include/db_manager.h index 8042d36..67f0f1d 100644 --- a/core/include/db_manager.h +++ b/core/include/db_manager.h @@ -29,6 +29,8 @@ #include #include +#include "include/acl_roles_handler.h" + /** * @brief A class used to handle database interaction. * @@ -138,14 +140,14 @@ class DBManager : public QObject * @param username The username clients can use to log in with. * @param salt The salt to obfuscate the password with. * @param password The user's password. - * @param acl The user's authority bitflag -- what special permissions does the user have. + * @param acl The ACL role identifier. * * @return False if the user already exists, true if the user was successfully created. * - * @see AOClient::cmdLogin() and AOClient::cmdLogout() for the username and password's contexts. - * @see AOClient::ACLFlags for the potential special permissions a user may have. + * @see AOClient#cmdLogin and AOClient#cmdLogout for the username and password's contexts. + * @see ACLRolesHandler for details regarding ACL roles and ACL role identifiers. */ - bool createUser(QString username, QString salt, QString password, unsigned long long acl); + bool createUser(QString username, QString salt, QString password, QString acl); /** * @brief Deletes an authorised user from the database. @@ -157,16 +159,15 @@ class DBManager : public QObject bool deleteUser(QString username); /** - * @brief Gets the permissions of a given user. + * @brief Gets the ACL role of a given user. * - * @param moderator_name The authorised user's name. + * @param username The authorised user's name. * - * @return `0` if `moderator_name` is empty, `0` if such user does not exist in the Users table, - * or the primitive representation of an AOClient::ACLFlags permission matrix if neither of the above are true. + * @return The name identifier of a ACL role. * - * @see AOClient::ACLFlags for the potential permissions a user may have. + * @see ACLRolesHandler for details about ACL roles. */ - unsigned long long getACL(QString moderator_name); + QString getACL(QString f_username); /** * @brief Authenticates a given user. @@ -180,38 +181,17 @@ class DBManager : public QObject bool authenticate(QString username, QString password); /** - * @brief Updates the permissions of a given user. + * @brief Updates the ACL role identifier of a given user. * - * @details This function can add or remove permissions as needed. - * `acl` determines what permissions are modified, while `mode` determines whether said permissions are - * added or removed. + * @details This function **DOES NOT** modify the ACL role itself. It is simply an identifier that determines which ACL role the user is linked to. * - * `acl` **is not** the user's current permissions *or* the sum permissions you want for the user at the end - * -- it is the 'difference' between the user's current and desired permissions. + * @param username The username of the user to be updated. * - * If `acl` is `"NONE"`, then no matter the mode, the user's permissions are cleared. - * - * For some practical examples, consult this example table: - * - * | Starting permissions | `acl` | `mode` | Resulting permissions | - * | -------------------: | :---------: | :-----: | :-------------------- | - * | KICK | BAN | `TRUE` | KICK, BAN | - * | BAN, KICK | BAN | `TRUE` | KICK, BAN | - * | KICK | BAN, BGLOCK | `TRUE` | KICK, BAN, BGLOCK | - * | BGLOCK, BAN, KICK | NONE | `TRUE` | NONE | - * | KICK | BAN | `FALSE` | KICK | - * | BAN, KICK | BAN | `FALSE` | KICK | - * | BGLOCK, BAN, KICK | BAN, BGLOCK | `FALSE` | KICK | - * | BGLOCK, BAN, KICK | NONE | `FALSE` | NONE | - * - * @param username The username of the user whose permissions should be updated. - * @param acl The primitive representation of the permission matrix being modified. - * @param mode If true, the permissions described in `acl` are *added* to the user; - * if false, they are removed instead. + * @param acl The ACL role identifier. * * @return True if the modification was successful, false if the user does not exist in the records. */ - bool updateACL(QString username, unsigned long long acl, bool mode); + bool updateACL(QString username, QString acl); /** * @brief Returns a list of the recorded users' usernames, ordered by ID. diff --git a/core/include/server.h b/core/include/server.h index 88bb3f3..dff787d 100644 --- a/core/include/server.h +++ b/core/include/server.h @@ -31,6 +31,7 @@ #include "include/aopacket.h" +class ACLRolesHandler; class Advertiser; class AOClient; class AreaData; @@ -285,6 +286,8 @@ class Server : public QObject */ DBManager *getDatabaseManager(); + ACLRolesHandler *getACLRolesHandler(); + /** * @brief The server-wide global timer. */ @@ -508,6 +511,8 @@ class Server : public QObject */ DBManager *db_manager; + ACLRolesHandler *acl_roles_handler; + /** * @brief Connects new AOClient to logger and disconnect handling. **/ diff --git a/core/src/acl_roles_handler.cpp b/core/src/acl_roles_handler.cpp new file mode 100644 index 0000000..223482d --- /dev/null +++ b/core/src/acl_roles_handler.cpp @@ -0,0 +1,283 @@ +#include "include/acl_roles_handler.h" + +#include +#include + +const QString ACLRolesHandler::NONE_ID = "NONE"; + +const QString ACLRolesHandler::SUPER_ID = "SUPER"; + +const QHash ACLRolesHandler::readonly_roles{ + {ACLRolesHandler::NONE_ID, ACLRole(ACLRole::NONE)}, + {ACLRolesHandler::SUPER_ID, ACLRole(ACLRole::SUPER)}, +}; + +const QHash ACLRole::permission_captions{ + { + ACLRole::Permission::KICK, + "kick", + }, + { + ACLRole::Permission::BAN, + "ban", + }, + { + ACLRole::Permission::BGLOCK, + "lock_background", + }, + { + ACLRole::Permission::MODIFY_USERS, + "modify_users", + }, + { + ACLRole::Permission::CM, + "set_gamemaster", + }, + { + ACLRole::Permission::GLOBAL_TIMER, + "use_global_timer", + }, + { + ACLRole::Permission::EVI_MOD, + "modify_evidence", + }, + { + ACLRole::Permission::MOTD, + "set_motd", + }, + { + ACLRole::Permission::ANNOUNCE, + "announcer", + }, + { + ACLRole::Permission::MODCHAT, + "chat_moderator", + }, + { + ACLRole::Permission::MUTE, + "mute", + }, + { + ACLRole::Permission::UNCM, + "remove_gamemaster", + }, + { + ACLRole::Permission::SAVETEST, + "save_testimony", + }, + { + ACLRole::Permission::FORCE_CHARSELECT, + "force_charselect", + }, + { + ACLRole::Permission::BYPASS_LOCKS, + "bypass_locks", + }, + { + ACLRole::Permission::IGNORE_BGLIST, + "ignore_bg_list", + }, + { + ACLRole::Permission::SEND_NOTICE, + "send_notice", + }, + { + ACLRole::Permission::JUKEBOX, + "jukebox", + }, + { + ACLRole::Permission::SUPER, + "super", + }, +}; + +ACLRole::ACLRole() {} + +ACLRole::ACLRole(ACLRole::Permissions f_permissions) : + m_permissions(f_permissions) +{ +} + +ACLRole::~ACLRole() {} + +ACLRole::Permissions ACLRole::getPermissions() const +{ + return m_permissions; +} + +bool ACLRole::checkPermission(Permission f_permission) const +{ + if (f_permission == ACLRole::NONE) { + return true; + } + return m_permissions.testFlag(f_permission); +} + +void ACLRole::setPermissions(ACLRole::Permissions f_permissions) +{ + m_permissions = f_permissions; +} + +void ACLRole::setPermission(Permission f_permission, bool f_mode) +{ + m_permissions.setFlag(f_permission, f_mode); +} + +ACLRolesHandler::ACLRolesHandler(QObject *parent) : + QObject(parent) +{} + +ACLRolesHandler::~ACLRolesHandler() {} + +bool ACLRolesHandler::roleExists(QString f_id) +{ + f_id = f_id.toUpper(); + return readonly_roles.contains(f_id) || m_roles.contains(f_id); +} + +ACLRole ACLRolesHandler::getRoleById(QString f_id) +{ + f_id = f_id.toUpper(); + return readonly_roles.contains(f_id) ? readonly_roles.value(f_id) : m_roles.value(f_id); +} + +bool ACLRolesHandler::insertRole(QString f_id, ACLRole f_role) +{ + f_id = f_id.toUpper(); + if (readonly_roles.contains(f_id)) { + return false; + } + m_roles.insert(f_id, f_role); + return true; +} + +bool ACLRolesHandler::removeRole(QString f_id) +{ + f_id = f_id.toUpper(); + if (readonly_roles.contains(f_id)) { + return false; + } + else if (!m_roles.contains(f_id)) { + return false; + } + m_roles.remove(f_id); + return true; +} + +void ACLRolesHandler::clearRoles() +{ + m_roles.clear(); +} + +bool ACLRolesHandler::loadFile(QString f_file_name) +{ + QSettings l_settings(f_file_name, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) { + switch (l_settings.status()) { + case QSettings::AccessError: + qWarning() << "ACLRolesHandler" + << "error: failed to open file; aborting (" << f_file_name << ")"; + break; + + case QSettings::FormatError: + qWarning() << "ACLRolesHandler" + << "error: file is malformed; aborting (" << f_file_name << ")"; + break; + + default: + qWarning() << "ACLRolesHandler" + << "error: unknown error; aborting; aborting (" << f_file_name << ")"; + break; + } + + return false; + } + + m_roles.clear(); + QStringList l_role_records; + const QStringList l_group_list = l_settings.childGroups(); + for (const QString &i_group : l_group_list) { + const QString l_upper_group = i_group.toUpper(); + if (readonly_roles.contains(l_upper_group)) { + qWarning() << "ACLRolesHandler warning: cannot modify role;" << i_group << "is read-only"; + continue; + } + + l_settings.beginGroup(i_group); + if (l_role_records.contains(l_upper_group)) { + qWarning() << "ACLRolesHandler warning: role" << l_upper_group << "already exist! Overwriting."; + } + l_role_records.append(l_upper_group); + + ACLRole l_role; + const QList l_permissions = ACLRole::permission_captions.keys(); + for (const ACLRole::Permission &i_permission : l_permissions) { + l_role.setPermission(i_permission, l_settings.value(ACLRole::permission_captions.value(i_permission), false).toBool()); + } + m_roles.insert(l_upper_group, std::move(l_role)); + + l_settings.endGroup(); + } + + return true; +} + +bool ACLRolesHandler::saveFile(QString f_file_name) +{ + QSettings l_settings(f_file_name, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) { + switch (l_settings.status()) { + case QSettings::AccessError: + qWarning() << "ACLRolesHandler" + << "error: failed to open file; aborting (" << f_file_name << ")"; + break; + + case QSettings::FormatError: + qWarning() << "ACLRolesHandler" + << "error: file is malformed; aborting (" << f_file_name << ")"; + break; + + default: + qWarning() << "ACLRolesHandler" + << "error: unknown error; aborting; aborting (" << f_file_name << ")"; + break; + } + + return false; + } + + l_settings.clear(); + const QStringList l_role_id_list = m_roles.keys(); + for (const QString &l_role_id : l_role_id_list) { + const QString l_upper_role_id = l_role_id.toUpper(); + if (readonly_roles.contains(l_upper_role_id)) { + continue; + } + + const ACLRole i_role = m_roles.value(l_upper_role_id); + l_settings.beginGroup(l_upper_role_id); + if (i_role.checkPermission(ACLRole::SUPER)) { + l_settings.setValue(ACLRole::permission_captions.value(ACLRole::SUPER), true); + } + else { + const QList l_permissions = ACLRole::permission_captions.keys(); + for (const ACLRole::Permission i_permission : l_permissions) { + if (!i_role.checkPermission(i_permission)) { + continue; + } + l_settings.setValue(ACLRole::permission_captions.value(i_permission), true); + } + } + l_settings.endGroup(); + } + l_settings.sync(); + if (l_settings.status() != QSettings::NoError) { + qWarning() << "ACLRolesHandler" + << "error: failed to write file; aborting (" << f_file_name << ")"; + return false; + } + + return true; +} diff --git a/core/src/aoclient.cpp b/core/src/aoclient.cpp index 2f870f9..6f2d8ee 100644 --- a/core/src/aoclient.cpp +++ b/core/src/aoclient.cpp @@ -87,13 +87,13 @@ void AOClient::handlePacket(AOPacket packet) qDebug() << "Received packet:" << packet.header << ":" << packet.contents << "args length:" << packet.contents.length(); #endif AreaData *l_area = server->getAreaById(m_current_area); - PacketInfo l_info = packets.value(packet.header, {false, 0, &AOClient::pktDefault}); + PacketInfo l_info = packets.value(packet.header, {ACLRole::NONE, 0, &AOClient::pktDefault}); if (packet.contents.join("").size() > 16384) { return; } - if (!checkAuth(l_info.acl_mask)) { + if (!checkPermission(l_info.acl_permission)) { return; } @@ -120,7 +120,7 @@ void AOClient::changeArea(int new_area) sendServerMessage("You are already in area " + server->getAreaName(m_current_area)); return; } - if (server->getAreaById(new_area)->lockStatus() == AreaData::LockStatus::LOCKED && !server->getAreaById(new_area)->invited().contains(m_id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) { + if (server->getAreaById(new_area)->lockStatus() == AreaData::LockStatus::LOCKED && !server->getAreaById(new_area)->invited().contains(m_id) && !checkPermission(ACLRole::BYPASS_LOCKS)) { sendServerMessage("Area " + server->getAreaName(new_area) + " is locked."); return; } @@ -202,9 +202,9 @@ void AOClient::changePosition(QString new_pos) void AOClient::handleCommand(QString command, int argc, QStringList argv) { - CommandInfo l_info = commands.value(command, {false, -1, &AOClient::cmdDefault}); + CommandInfo l_info = commands.value(command, {ACLRole::NONE, -1, &AOClient::cmdDefault}); - if (!checkAuth(l_info.acl_mask)) { + if (!checkPermission(l_info.acl_permission)) { sendServerMessage("You do not have permission to use that command."); return; } @@ -331,31 +331,22 @@ void AOClient::sendServerBroadcast(QString message) server->broadcast(AOPacket("CT", {ConfigManager::serverName(), message, "1"})); } -bool AOClient::checkAuth(unsigned long long acl_mask) +bool AOClient::checkPermission(ACLRole::Permission f_permission) const { -#ifdef SKIP_AUTH - return true; -#endif - if (acl_mask != ACLFlags.value("NONE")) { - if (acl_mask == ACLFlags.value("CM")) { - AreaData *l_area = server->getAreaById(m_current_area); - if (l_area->owners().contains(m_id)) - return true; - } - else if (!m_authenticated) { - return false; - } - switch (ConfigManager::authType()) { - case DataTypes::AuthType::SIMPLE: - return m_authenticated; - break; - case DataTypes::AuthType::ADVANCED: - unsigned long long l_user_acl = server->getDatabaseManager()->getACL(m_moderator_name); - return (l_user_acl & acl_mask) != 0; - break; - } + if (f_permission == ACLRole::NONE) { + return true; } - return true; + + if (!isAuthenticated()) { + return false; + } + + if (ConfigManager::authType() == DataTypes::AuthType::SIMPLE) { + return true; + } + + const ACLRole l_role = server->getACLRolesHandler()->getRoleById(m_acl_role_id); + return l_role.checkPermission(f_permission); } QString AOClient::getIpid() const @@ -373,6 +364,11 @@ bool AOClient::hasJoined() const return m_joined; } +bool AOClient::isAuthenticated() const +{ + return m_authenticated; +} + Server *AOClient::getServer() { return server; } void AOClient::onAfkTimeout() diff --git a/core/src/commands/area.cpp b/core/src/commands/area.cpp index 15c3a1b..1deee25 100644 --- a/core/src/commands/area.cpp +++ b/core/src/commands/area.cpp @@ -78,7 +78,7 @@ void AOClient::cmdUnCM(int argc, QStringList argv) l_uid = m_id; sendServerMessage("You are no longer CM in this area."); } - else if (checkAuth(ACLFlags.value("UNCM")) && argc == 1) { + else if (checkPermission(ACLRole::UNCM) && argc == 1) { bool conv_ok = false; l_uid = argv[0].toInt(&conv_ok); if (!conv_ok) { @@ -366,7 +366,7 @@ void AOClient::cmdJudgeLog(int argc, QStringList argv) } QString l_message = l_area->judgelog().join("\n"); // Judgelog contains an IPID, so we shouldn't send that unless the caller has appropriate permissions - if (checkAuth(ACLFlags.value("KICK")) == 1 || checkAuth(ACLFlags.value("BAN")) == 1) { + if (checkPermission(ACLRole::KICK) || checkPermission(ACLRole::BAN)) { sendServerMessage(l_message); } else { diff --git a/core/src/commands/authentication.cpp b/core/src/commands/authentication.cpp index 3a63261..8a2d17a 100644 --- a/core/src/commands/authentication.cpp +++ b/core/src/commands/authentication.cpp @@ -90,7 +90,7 @@ void AOClient::cmdSetRootPass(int argc, QStringList argv) #endif QString l_salt = QStringLiteral("%1").arg(l_salt_number, 16, 16, QLatin1Char('0')); - server->getDatabaseManager()->createUser("root", l_salt, argv[0], ACLFlags.value("SUPER")); + server->getDatabaseManager()->createUser("root", l_salt, argv[0], ACLRolesHandler::SUPER_ID); } void AOClient::cmdAddUser(int argc, QStringList argv) @@ -111,8 +111,8 @@ void AOClient::cmdAddUser(int argc, QStringList argv) #endif QString l_salt = QStringLiteral("%1").arg(l_salt_number, 16, 16, QLatin1Char('0')); - if (server->getDatabaseManager()->createUser(argv[0], l_salt, argv[1], ACLFlags.value("NONE"))) - sendServerMessage("Created user " + argv[0] + ".\nUse /addperm to modify their permissions."); + if (server->getDatabaseManager()->createUser(argv[0], l_salt, argv[1], ACLRolesHandler::NONE_ID)) + sendServerMessage("Created user " + argv[0] + ".\nUse /setperms to modify their permissions."); else sendServerMessage("Unable to create user " + argv[0] + ".\nDoes a user with that name already exist?"); } @@ -129,125 +129,73 @@ void AOClient::cmdRemoveUser(int argc, QStringList argv) void AOClient::cmdListPerms(int argc, QStringList argv) { - unsigned long long l_user_acl = server->getDatabaseManager()->getACL(m_moderator_name); + const ACLRole l_role = server->getACLRolesHandler()->getRoleById(m_acl_role_id); + + ACLRole l_target_role = l_role; QStringList l_message; - const QStringList l_keys = ACLFlags.keys(); if (argc == 0) { - // Just print out all permissions available to the user. l_message.append("You have been given the following permissions:"); - for (const QString &l_perm : l_keys) { - if (l_perm == "NONE") - ; // don't need to list this one - else if (l_perm == "SUPER") { - if (l_user_acl == ACLFlags.value("SUPER")) // This has to be checked separately, because SUPER & anything will always be truthy - l_message.append("SUPER (Be careful! This grants the user all permissions.)"); - } - else if ((ACLFlags.value(l_perm) & l_user_acl) == 0) - ; // user doesn't have this permission, don't print it - else - l_message.append(l_perm); - } } else { - if ((l_user_acl & ACLFlags.value("MODIFY_USERS")) == 0) { + if (!l_role.checkPermission(ACLRole::MODIFY_USERS)) { sendServerMessage("You do not have permission to view other users' permissions."); return; } l_message.append("User " + argv[0] + " has the following permissions:"); - unsigned long long l_acl = server->getDatabaseManager()->getACL(argv[0]); - if (l_acl == 0) { - sendServerMessage("This user either doesn't exist, or has no permissions set."); - return; - } + l_target_role = server->getACLRolesHandler()->getRoleById(argv[0]); + } - for (const QString &l_perm : l_keys) { - if ((ACLFlags.value(l_perm) & l_acl) != 0 && l_perm != "SUPER") { - l_message.append(l_perm); + if (l_target_role.getPermissions() == ACLRole::NONE) { + l_message.append("NONE"); + } + else if (l_target_role.checkPermission(ACLRole::SUPER)) { + l_message.append("SUPER (Be careful! This grants the user all permissions.)"); + } + else { + const QList l_permissions = ACLRole::permission_captions.keys(); + for (const ACLRole::Permission i_permission : l_permissions) { + if (l_target_role.checkPermission(i_permission)) { + l_message.append(ACLRole::permission_captions.value(i_permission)); } } } sendServerMessage(l_message.join("\n")); } -void AOClient::cmdAddPerms(int argc, QStringList argv) +void AOClient::cmdSetPerms(int argc, QStringList argv) { Q_UNUSED(argc); - unsigned long long l_user_acl = server->getDatabaseManager()->getACL(m_moderator_name); - argv[1] = argv[1].toUpper(); - const QStringList l_keys = ACLFlags.keys(); - - if (!l_keys.contains(argv[1])) { - sendServerMessage("That permission doesn't exist!"); + const QString l_target_acl = argv[1]; + if (!server->getACLRolesHandler()->roleExists(l_target_acl)) { + sendServerMessage("That role doesn't exist!"); return; } - if (argv[1] == "SUPER") { - if (l_user_acl != ACLFlags.value("SUPER")) { - // This has to be checked separately, because SUPER & anything will always be truthy - sendServerMessage("You aren't allowed to add that permission!"); - return; - } - } - if (argv[1] == "NONE") { - sendServerMessage("Added no permissions!"); + if (l_target_acl == ACLRolesHandler::SUPER_ID && !checkPermission(ACLRole::SUPER)) { + sendServerMessage("You aren't allowed to set that role!"); return; } - unsigned long long l_newperm = ACLFlags.value(argv[1]); - if ((l_newperm & l_user_acl) != 0) { - if (server->getDatabaseManager()->updateACL(argv[0], l_newperm, true)) - sendServerMessage("Successfully added permission " + argv[1] + " to user " + argv[0]); - else - sendServerMessage(argv[0] + " wasn't found!"); + const QString l_target_username = argv[0]; + if (l_target_username == "root") { + sendServerMessage("You can't change root's role!"); return; } - sendServerMessage("You aren't allowed to add that permission!"); + if (server->getDatabaseManager()->updateACL(l_target_username, l_target_acl)) { + sendServerMessage("Successfully changed role " + l_target_acl + " to user " + l_target_username); + } + else { + sendServerMessage(l_target_username + " wasn't found!"); + } } void AOClient::cmdRemovePerms(int argc, QStringList argv) { - Q_UNUSED(argc); - - unsigned long long l_user_acl = server->getDatabaseManager()->getACL(m_moderator_name); - argv[1] = argv[1].toUpper(); - - const QStringList l_keys = ACLFlags.keys(); - - if (!l_keys.contains(argv[1])) { - sendServerMessage("That permission doesn't exist!"); - return; - } - - if (argv[0] == "root") { - sendServerMessage("You cannot change the permissions of the root account!"); - return; - } - - if (argv[1] == "SUPER") { - if (l_user_acl != ACLFlags.value("SUPER")) { - // This has to be checked separately, because SUPER & anything will always be truthy - sendServerMessage("You aren't allowed to remove that permission!"); - return; - } - } - if (argv[1] == "NONE") { - sendServerMessage("Removed no permissions!"); - return; - } - - unsigned long long l_newperm = ACLFlags.value(argv[1]); - if ((l_newperm & l_user_acl) != 0) { - if (server->getDatabaseManager()->updateACL(argv[0], l_newperm, false)) - sendServerMessage("Successfully removed permission " + argv[1] + " from user " + argv[0]); - else - sendServerMessage(argv[0] + " wasn't found!"); - return; - } - - sendServerMessage("You aren't allowed to remove that permission!"); + argv.append(ACLRolesHandler::NONE_ID); + cmdSetPerms(argc, argv); } void AOClient::cmdListUsers(int argc, QStringList argv) @@ -269,6 +217,7 @@ void AOClient::cmdLogout(int argc, QStringList argv) return; } m_authenticated = false; + m_acl_role_id = ""; m_moderator_name = ""; sendPacket("AUTH", {"-1"}); // Client: "You were logged out." } @@ -284,8 +233,9 @@ void AOClient::cmdChangePassword(int argc, QStringList argv) } l_username = m_moderator_name; } - else if (argc == 2 && checkAuth(ACLFlags.value("SUPER"))) { - l_username = argv[1]; + else if (argc == 2 && checkPermission(ACLRole::SUPER)) { + l_username = argv[0]; + l_password = argv[1]; } else { sendServerMessage("Invalid command syntax."); diff --git a/core/src/commands/casing.cpp b/core/src/commands/casing.cpp index 7ee4419..4a6141d 100644 --- a/core/src/commands/casing.cpp +++ b/core/src/commands/casing.cpp @@ -214,7 +214,7 @@ void AOClient::cmdSaveTestimony(int argc, QStringList argv) Q_UNUSED(argc); bool l_permission_found = false; - if (checkAuth(ACLFlags.value("SAVETEST"))) + if (checkPermission(ACLRole::SAVETEST)) l_permission_found = true; if (m_testimony_saving == true) diff --git a/core/src/commands/messaging.cpp b/core/src/commands/messaging.cpp index 7b43897..d15779f 100644 --- a/core/src/commands/messaging.cpp +++ b/core/src/commands/messaging.cpp @@ -479,7 +479,7 @@ void AOClient::cmdCharSelect(int argc, QStringList argv) sendPacket("DONE"); } else { - if (!checkAuth(ACLFlags.value("FORCE_CHARSELECT"))) { + if (!checkPermission(ACLRole::FORCE_CHARSELECT)) { sendServerMessage("You do not have permission to force another player to character select!"); return; } diff --git a/core/src/commands/moderation.cpp b/core/src/commands/moderation.cpp index 1a55653..c417858 100644 --- a/core/src/commands/moderation.cpp +++ b/core/src/commands/moderation.cpp @@ -141,8 +141,10 @@ void AOClient::cmdMods(int argc, QStringList argv) for (AOClient *l_client : l_clients) { if (l_client->m_authenticated) { l_entries << "---"; - if (ConfigManager::authType() != DataTypes::AuthType::SIMPLE) + if (ConfigManager::authType() != DataTypes::AuthType::SIMPLE) { l_entries << "Moderator: " + l_client->m_moderator_name; + l_entries << "Role:" << l_client->m_acl_role_id; + } l_entries << "OOC name: " + l_client->m_ooc_name; l_entries << "ID: " + QString::number(l_client->m_id); l_entries << "Area: " + QString::number(l_client->m_current_area); @@ -165,7 +167,7 @@ void AOClient::cmdCommands(int argc, QStringList argv) QMap::const_iterator i; for (i = commands.constBegin(); i != commands.constEnd(); ++i) { CommandInfo info = i.value(); - if (checkAuth(info.acl_mask)) { // if we are allowed to use this command + if (checkPermission(info.acl_permission)) { // if we are allowed to use this command l_entries << "/" + i.key(); } } @@ -193,7 +195,7 @@ void AOClient::cmdMOTD(int argc, QStringList argv) sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n============="); } else if (argc > 0) { - if (checkAuth(ACLFlags.value("MOTD"))) { + if (checkPermission(ACLRole::MOTD)) { QString l_MOTD = argv.join(" "); ConfigManager::setMotd(l_MOTD); sendServerMessage("MOTD has been changed."); diff --git a/core/src/commands/music.cpp b/core/src/commands/music.cpp index 00a60d1..8bf03ec 100644 --- a/core/src/commands/music.cpp +++ b/core/src/commands/music.cpp @@ -127,7 +127,7 @@ void AOClient::cmdToggleJukebox(int argc, QStringList argv) Q_UNUSED(argc); Q_UNUSED(argv); - if (checkAuth(ACLFlags.value("CM")) | checkAuth(ACLFlags.value("Jukebox"))) { + if (checkPermission(ACLRole::CM) | checkPermission(ACLRole::JUKEBOX)) { AreaData *l_area = server->getAreaById(m_current_area); l_area->toggleJukebox(); QString l_state = l_area->isjukeboxEnabled() ? "enabled." : "disabled."; diff --git a/core/src/commands/roleplay.cpp b/core/src/commands/roleplay.cpp index 22dd5bc..d5fae4e 100644 --- a/core/src/commands/roleplay.cpp +++ b/core/src/commands/roleplay.cpp @@ -84,7 +84,7 @@ void AOClient::cmdTimer(int argc, QStringList argv) // Check against permissions if global timer is selected QTimer *l_requested_timer; if (l_timer_id == 0) { - if (!checkAuth(ACLFlags.value("GLOBAL_TIMER"))) { + if (!checkPermission(ACLRole::GLOBAL_TIMER)) { sendServerMessage("You are not authorized to alter the global timer."); return; } diff --git a/core/src/db_manager.cpp b/core/src/db_manager.cpp index 595f011..afae5b1 100644 --- a/core/src/db_manager.cpp +++ b/core/src/db_manager.cpp @@ -171,11 +171,11 @@ bool DBManager::invalidateBan(int id) return true; } -bool DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl) +bool DBManager::createUser(QString f_username, QString f_salt, QString f_password, QString f_acl) { QSqlQuery username_exists; username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?"); - username_exists.addBindValue(username); + username_exists.addBindValue(f_username); username_exists.exec(); if (username_exists.first()) @@ -185,15 +185,15 @@ bool DBManager::createUser(QString username, QString salt, QString password, uns QString salted_password; QMessageAuthenticationCode hmac(QCryptographicHash::Sha256); - hmac.setKey(salt.toUtf8()); - hmac.addData(password.toUtf8()); + hmac.setKey(f_salt.toUtf8()); + hmac.addData(f_password.toUtf8()); salted_password = hmac.result().toHex(); query.prepare("INSERT INTO users(USERNAME, SALT, PASSWORD, ACL) VALUES(?, ?, ?, ?)"); - query.addBindValue(username); - query.addBindValue(salt); + query.addBindValue(f_username); + query.addBindValue(f_salt); query.addBindValue(salted_password); - query.addBindValue(acl); + query.addBindValue(f_acl); query.exec(); return true; @@ -226,7 +226,7 @@ bool DBManager::deleteUser(QString username) } } -unsigned long long DBManager::getACL(QString moderator_name) +QString DBManager::getACL(QString moderator_name) { if (moderator_name == "") return 0; @@ -235,7 +235,7 @@ unsigned long long DBManager::getACL(QString moderator_name) query.exec(); if (!query.first()) return 0; - return query.value(0).toULongLong(); + return query.value(0).toString(); } bool DBManager::authenticate(QString username, QString password) @@ -263,30 +263,21 @@ bool DBManager::authenticate(QString username, QString password) return salted_password == stored_pass; } -bool DBManager::updateACL(QString username, unsigned long long acl, bool mode) +bool DBManager::updateACL(QString f_username, QString f_acl) { - QSqlQuery username_exists; - username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?"); - username_exists.addBindValue(username); - username_exists.exec(); + QSqlQuery l_username_exists; + l_username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?"); + l_username_exists.addBindValue(f_username); + l_username_exists.exec(); - if (!username_exists.first()) + if (!l_username_exists.first()) return false; - unsigned long long old_acl = username_exists.value(0).toULongLong(); - unsigned long long new_acl; - if (mode) // adding perm - new_acl = old_acl | acl; - else // removing perm - new_acl = old_acl & ~acl; - if (acl == 0) // Allow clearing all perms via adding perm "NONE" - new_acl = 0; - - QSqlQuery update_acl; - update_acl.prepare("UPDATE users SET ACL = ? WHERE USERNAME = ?"); - update_acl.addBindValue(new_acl); - update_acl.addBindValue(username); - update_acl.exec(); + QSqlQuery l_update_acl; + l_update_acl.prepare("UPDATE users SET ACL = ? WHERE USERNAME = ?"); + l_update_acl.addBindValue(f_acl); + l_update_acl.addBindValue(f_username); + l_update_acl.exec(); return true; } diff --git a/core/src/packets.cpp b/core/src/packets.cpp index 328c09f..fb5c532 100644 --- a/core/src/packets.cpp +++ b/core/src/packets.cpp @@ -308,7 +308,7 @@ void AOClient::pktChangeMusic(AreaData *area, int argc, QStringList argv, AOPack sendServerMessage("You are blocked from changing the music."); return; } - if (!area->isMusicAllowed() && !checkAuth(ACLFlags.value("CM"))) { + if (!area->isMusicAllowed() && !checkPermission(ACLRole::CM)) { sendServerMessage("Music is disabled in this area."); return; } @@ -613,7 +613,7 @@ void AOClient::updateEvidenceList(AreaData *area) const QList l_area_evidence = area->evidence(); for (const AreaData::Evidence &evidence : l_area_evidence) { - if (!checkAuth(ACLFlags.value("CM")) && area->eviMod() == AreaData::EvidenceMod::HIDDEN_CM) { + if (!checkPermission(ACLRole::CM) && area->eviMod() == AreaData::EvidenceMod::HIDDEN_CM) { QRegularExpression l_regex(""); QRegularExpressionMatch l_match = l_regex.match(evidence.description); if (l_match.hasMatch()) { @@ -648,7 +648,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // Spectators cannot use IC return l_invalid; AreaData *area = server->getAreaById(m_current_area); - if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(m_id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) + if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(m_id) && !checkPermission(ACLRole::BYPASS_LOCKS)) // Non-invited players cannot speak in spectatable areas return l_invalid; @@ -1007,7 +1007,7 @@ bool AOClient::checkEvidenceAccess(AreaData *area) return true; case AreaData::EvidenceMod::CM: case AreaData::EvidenceMod::HIDDEN_CM: - return checkAuth(ACLFlags.value("CM")); + return checkPermission(ACLRole::CM); case AreaData::EvidenceMod::MOD: return m_authenticated; default: @@ -1043,6 +1043,7 @@ void AOClient::loginAttempt(QString message) sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC m_authenticated = true; + m_acl_role_id = ACLRolesHandler::SUPER_ID; } else { sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." @@ -1062,8 +1063,9 @@ void AOClient::loginAttempt(QString message) QString username = l_login[0]; QString password = l_login[1]; if (server->getDatabaseManager()->authenticate(username, password)) { - m_moderator_name = username; m_authenticated = true; + m_acl_role_id = server->getDatabaseManager()->getACL(username); + m_moderator_name = username; sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." if (m_version.release <= 2 && m_version.major <= 9 && m_version.minor <= 0) sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC diff --git a/core/src/server.cpp b/core/src/server.cpp index 108ef2f..17ee535 100644 --- a/core/src/server.cpp +++ b/core/src/server.cpp @@ -17,6 +17,7 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/server.h" +#include "include/acl_roles_handler.h" #include "include/advertiser.h" #include "include/aoclient.h" #include "include/aopacket.h" @@ -42,7 +43,10 @@ Server::Server(int p_port, int p_ws_port, QObject *parent) : proxy->start(); timer = new QTimer(this); - db_manager = new DBManager(); + db_manager = new DBManager; + + acl_roles_handler = new ACLRolesHandler; + acl_roles_handler->loadFile("config/acl_roles.ini"); // We create it, even if its not used later on. discord = new Discord(this); @@ -304,7 +308,7 @@ void Server::broadcast(AOPacket packet, TARGET_TYPE target) for (AOClient *l_client : qAsConst(m_clients)) { switch (target) { case TARGET_TYPE::MODCHAT: - if (l_client->checkAuth(l_client->ACLFlags.value("MODCHAT"))) { + if (l_client->checkPermission(ACLRole::MODCHAT)) { l_client->sendPacket(packet); } break; @@ -324,7 +328,7 @@ void Server::broadcast(AOPacket packet, AOPacket other_packet, TARGET_TYPE targe switch (target) { case TARGET_TYPE::AUTHENTICATED: for (AOClient *l_client : qAsConst(m_clients)) { - if (l_client->m_authenticated) { + if (l_client->isAuthenticated()) { l_client->sendPacket(other_packet); } else { @@ -454,6 +458,11 @@ DBManager *Server::getDatabaseManager() return db_manager; } +ACLRolesHandler *Server::getACLRolesHandler() +{ + return acl_roles_handler; +} + void Server::allowMessage() { m_can_send_ic_messages = true; @@ -529,6 +538,7 @@ Server::~Server() server->deleteLater(); proxy->deleteLater(); discord->deleteLater(); + acl_roles_handler->deleteLater(); delete db_manager; } diff --git a/tests/tests.pro b/tests/tests.pro index 6219044..f7cb3f5 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -2,4 +2,5 @@ TEMPLATE = subdirs SUBDIRS += \ unittest_area \ - unittest_music_manager + unittest_music_manager \ + unittest_acl_roles_handler diff --git a/tests/unittest_acl_roles_handler/tst_unittest_acl_roles_handler.cpp b/tests/unittest_acl_roles_handler/tst_unittest_acl_roles_handler.cpp new file mode 100644 index 0000000..b9e33c7 --- /dev/null +++ b/tests/unittest_acl_roles_handler/tst_unittest_acl_roles_handler.cpp @@ -0,0 +1,193 @@ +#include +#include +#include + +#include + +namespace tests { +namespace unittests { + +/** + * @brief Unit Tester class for ACL roles-related functions. + */ +class tst_ACLRolesHandler : public QObject +{ + Q_OBJECT + + public: + ACLRolesHandler *m_handler; + + private slots: + /** + * @brief Initialises every tests with creating a new ACLRolesHandler. + */ + void init(); + + /** + * @brief Tests the general state of read-only roles. + */ + void checkReadOnlyRoles(); + + /** + * @brief Tests removal of read-only roles. + */ + void removeReadOnlyRoles(); + + /** + * @brief Tests general modifications of read-only roles. + */ + void replaceReadOnlyRoles(); + + /** + * @brief Tests general modifications of roles. + */ + void modifyRoles(); + + /** + * @brief Tests clearance of roles. + */ + void clearAllRoles(); +}; + +void tst_ACLRolesHandler::init() +{ + m_handler = new ACLRolesHandler; +} + +void tst_ACLRolesHandler::checkReadOnlyRoles() +{ + { + const QString l_role_name = ACLRolesHandler::NONE_ID; + + // Checks if the role exists + QCOMPARE(m_handler->roleExists(ACLRolesHandler::NONE_ID), true); + + ACLRole l_role = m_handler->getRoleById(ACLRolesHandler::NONE_ID); + // Checks every permissions + QCOMPARE(l_role.checkPermission(ACLRole::NONE), true); + QCOMPARE(l_role.checkPermission(ACLRole::KICK), false); + QCOMPARE(l_role.checkPermission(ACLRole::BAN), false); + QCOMPARE(l_role.checkPermission(ACLRole::BGLOCK), false); + QCOMPARE(l_role.checkPermission(ACLRole::MODIFY_USERS), false); + QCOMPARE(l_role.checkPermission(ACLRole::CM), false); + QCOMPARE(l_role.checkPermission(ACLRole::GLOBAL_TIMER), false); + QCOMPARE(l_role.checkPermission(ACLRole::EVI_MOD), false); + QCOMPARE(l_role.checkPermission(ACLRole::MOTD), false); + QCOMPARE(l_role.checkPermission(ACLRole::ANNOUNCE), false); + QCOMPARE(l_role.checkPermission(ACLRole::MODCHAT), false); + QCOMPARE(l_role.checkPermission(ACLRole::MUTE), false); + QCOMPARE(l_role.checkPermission(ACLRole::UNCM), false); + QCOMPARE(l_role.checkPermission(ACLRole::SAVETEST), false); + QCOMPARE(l_role.checkPermission(ACLRole::FORCE_CHARSELECT), false); + QCOMPARE(l_role.checkPermission(ACLRole::BYPASS_LOCKS), false); + QCOMPARE(l_role.checkPermission(ACLRole::IGNORE_BGLIST), false); + QCOMPARE(l_role.checkPermission(ACLRole::SEND_NOTICE), false); + QCOMPARE(l_role.checkPermission(ACLRole::JUKEBOX), false); + QCOMPARE(l_role.checkPermission(ACLRole::SUPER), false); + } + + { + const QString l_role_name = ACLRolesHandler::SUPER_ID; + + // Checks if the role exists + QCOMPARE(m_handler->roleExists(l_role_name), true); + + ACLRole l_role = m_handler->getRoleById(l_role_name); + // Checks every permissions + QCOMPARE(l_role.checkPermission(ACLRole::NONE), true); + QCOMPARE(l_role.checkPermission(ACLRole::KICK), true); + QCOMPARE(l_role.checkPermission(ACLRole::BAN), true); + QCOMPARE(l_role.checkPermission(ACLRole::BGLOCK), true); + QCOMPARE(l_role.checkPermission(ACLRole::MODIFY_USERS), true); + QCOMPARE(l_role.checkPermission(ACLRole::CM), true); + QCOMPARE(l_role.checkPermission(ACLRole::GLOBAL_TIMER), true); + QCOMPARE(l_role.checkPermission(ACLRole::EVI_MOD), true); + QCOMPARE(l_role.checkPermission(ACLRole::MOTD), true); + QCOMPARE(l_role.checkPermission(ACLRole::ANNOUNCE), true); + QCOMPARE(l_role.checkPermission(ACLRole::MODCHAT), true); + QCOMPARE(l_role.checkPermission(ACLRole::MUTE), true); + QCOMPARE(l_role.checkPermission(ACLRole::UNCM), true); + QCOMPARE(l_role.checkPermission(ACLRole::SAVETEST), true); + QCOMPARE(l_role.checkPermission(ACLRole::FORCE_CHARSELECT), true); + QCOMPARE(l_role.checkPermission(ACLRole::BYPASS_LOCKS), true); + QCOMPARE(l_role.checkPermission(ACLRole::IGNORE_BGLIST), true); + QCOMPARE(l_role.checkPermission(ACLRole::SEND_NOTICE), true); + QCOMPARE(l_role.checkPermission(ACLRole::JUKEBOX), true); + QCOMPARE(l_role.checkPermission(ACLRole::SUPER), true); + } +} + +void tst_ACLRolesHandler::removeReadOnlyRoles() +{ + { // SUPER role + // Removes the role. This should fail. + QCOMPARE(m_handler->removeRole(ACLRolesHandler::SUPER_ID), false); + + // Checks if the role exists. + QCOMPARE(m_handler->roleExists(ACLRolesHandler::SUPER_ID), true); + } +} + +void tst_ACLRolesHandler::replaceReadOnlyRoles() +{ + { + // Attempts to overwrite a read-only role. This should fail. + QCOMPARE(m_handler->insertRole(ACLRolesHandler::NONE_ID, ACLRole(ACLRole::NONE)), false); + } +} + +void tst_ACLRolesHandler::modifyRoles() +{ + { + const QString l_role_id = "new_role"; + + // Checks if a the role exists. This should fail. + QCOMPARE(m_handler->roleExists(l_role_id), false); + + // Inserts a role. + QCOMPARE(m_handler->insertRole(l_role_id, ACLRole(ACLRole::KICK)), true); + + // Inserts a role again. + QCOMPARE(m_handler->insertRole(l_role_id, ACLRole(ACLRole::MODIFY_USERS)), true); + + // Checks if the role exists. + QCOMPARE(m_handler->roleExists(l_role_id), true); + + const ACLRole l_role = m_handler->getRoleById(l_role_id); + // Checks every permissions + QCOMPARE(l_role.checkPermission(ACLRole::NONE), true); + QCOMPARE(l_role.checkPermission(ACLRole::KICK), false); + QCOMPARE(l_role.checkPermission(ACLRole::MODIFY_USERS), true); + QCOMPARE(l_role.checkPermission(ACLRole::SUPER), false); + + // Removes the role. + QCOMPARE(m_handler->removeRole(l_role_id), true); + + // Removes the role again. This should fail. + QCOMPARE(m_handler->removeRole(l_role_id), false); + + // Checks if the role exists. This should fail. + QCOMPARE(m_handler->roleExists(l_role_id), false); + } +} + +void tst_ACLRolesHandler::clearAllRoles() +{ + { + const QString l_role_id = "new_role"; + + // Inserts a role. + QCOMPARE(m_handler->insertRole(l_role_id, ACLRole(ACLRole::KICK)), true); + + m_handler->clearRoles(); + // Checks if a the role exists. This should fail. + QCOMPARE(m_handler->roleExists(l_role_id), false); + } +} + +} +} + +QTEST_APPLESS_MAIN(tests::unittests::tst_ACLRolesHandler) + +#include "tst_unittest_acl_roles_handler.moc" diff --git a/tests/unittest_acl_roles_handler/unittest_acl_roles_handler.pro b/tests/unittest_acl_roles_handler/unittest_acl_roles_handler.pro new file mode 100644 index 0000000..384aea9 --- /dev/null +++ b/tests/unittest_acl_roles_handler/unittest_acl_roles_handler.pro @@ -0,0 +1,5 @@ +QT -= gui + +include(../tests_common.pri) + +SOURCES += tst_unittest_acl_roles_handler.cpp