Added command extension system (#12)

* Added command extension system

Resolve #10

* Added akashi definitions

* Updated headers to comply to the standard

* Added full definition to argument

* Clang-format pass

* Missing header for GCC

* Missing header for GCC

* Move method implementation to source file
This commit is contained in:
Leifa♥ 2022-04-28 01:15:44 +02:00 committed by Rosemary Witchaven
parent ec44039816
commit f307f728c9
20 changed files with 1043 additions and 260 deletions

View File

@ -0,0 +1,12 @@
[moderator]
ban = true
kick = true
mute = true
chat_moderator = true
[supervisor]
ban = true
kick = true
mute = true
chat_moderator = true
modify_users = true

View File

@ -0,0 +1,80 @@
[getarea]
aliases = ga
[getareas]
aliases = gas
[area_lock]
aliases = lock_area lock
[area_spectate]
aliases = spectatable
[area_unlock]
aliases = unlock_area unlock
[area_kick]
aliases = kick_area areakick
[background]
aliases = bg
[lock_background]
aliases = lock_bg lockbg bglock
[unlock_background]
aliases = unlock_bg unlockbg bgunlock
[roll]
aliases = r
[set_motd]
aliases = setmotd
[force_charselect]
aliases = forcecharselect
[notecard_reveal]
aliases = reveal_notecard notecardreveal
[notecard_clear]
aliases = clear_notecard notecardclear
[allow_blankposting]
aliases = allowblankposting
[forceimmediate]
aliases = force_noint_pres
[allow_iniswap]
aliases = allowiniswap
[ooc_mute]
aliases = mute_ooc oocmute
[ooc_unmute]
aliases = unmute_ooc oocunmute
[block_wtce]
aliases = blockwtce
[unblock_wtce]
aliases = unblockwtce
[block_dj]
aliases = blockdj
[unblock_dj]
aliases = unblockdj
[kick_uid]
aliases = kickuid
[update_ban]
aliases = updateban
[ignore_bglist]
aliases = ignorebglist
[ignore_bglist]
aliases = ignorebglist

View File

@ -29,6 +29,7 @@ SOURCES += \
src/aoclient.cpp \
src/aopacket.cpp \
src/area_data.cpp \
src/command_extension.cpp \
src/commands/area.cpp \
src/commands/authentication.cpp \
src/commands/casing.cpp \
@ -53,8 +54,10 @@ SOURCES += \
HEADERS += include/aoclient.h \
include/acl_roles_handler.h \
include/akashidefs.h \
include/aopacket.h \
include/area_data.h \
include/command_extension.h \
include/config_manager.h \
include/data_types.h \
include/db_manager.h \

View File

@ -41,7 +41,7 @@ class ACLRole
*
* @see ACLRoleHandler#loadFile and ACLRoleHandler#saveFile
*/
static const QHash<ACLRole::Permission, QString> permission_captions;
static const QHash<ACLRole::Permission, QString> PERMISSION_CAPTIONS;
/**
* @brief Constructs a role without any permissions.
@ -98,6 +98,7 @@ class ACLRole
*/
ACLRole::Permissions m_permissions;
};
Q_DECLARE_METATYPE(ACLRole::Permission)
class ACLRolesHandler : public QObject
{

17
core/include/akashidefs.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef AKASHIDEFS_H
#define AKASHIDEFS_H
#include <QString>
#include <qnamespace.h>
namespace akashi {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
using SplitBehavior = QString::SplitBehavior;
#else
using SplitBehavior = Qt::SplitBehaviorFlags;
#endif
const SplitBehavior KeepEmptyParts = SplitBehavior::KeepEmptyParts;
const SplitBehavior SkipEmptyParts = SplitBehavior::SkipEmptyParts;
}
#endif // AKASHIDEFS_H

View File

@ -46,6 +46,37 @@ class AOClient : public QObject
Q_OBJECT
public:
/**
* @brief Describes a command's details.
*/
struct CommandInfo
{
QVector<ACLRole::Permission> acl_permissions; //!< 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);
};
/**
* @property CommandInfo::action
*
* @brief A function reference that contains what the command actually does.
*
* @param int When called, this parameter will be filled with the argument count. @anchor commandArgc
* @param QStringList When called, this parameter will be filled the list of arguments. @anchor commandArgv
*/
/**
* @brief The list of commands available on the server.
*
* @details Generally called with the format of `/command parameters` in the out-of-character chat.
* @showinitializer
*
* @tparam QString The name of the command, without the leading slash.
* @tparam CommandInfo The details of the command.
* See @ref CommandInfo "the type's documentation" for more details.
*/
static const QMap<QString, CommandInfo> COMMANDS;
/**
* @brief Creates an instance of the AOClient class.
*
@ -1032,16 +1063,19 @@ class AOClient : public QObject
void cmdHelp(int argc, QStringList argv);
/**
* @brief Gets or sets the server's Message Of The Day.
*
* @details If called without arguments, gets the MOTD.
*
* If it has any number of arguments, it is set as the **MOTD**.
* @brief Gets the server's Message Of The Day.
*
* @iscommand
*/
void cmdMOTD(int argc, QStringList argv);
/**
* @brief Sets the server's Message Of The Day.
*
* @iscommand
*/
void cmdSetMOTD(int argc, QStringList argv);
/**
* @brief Gives a very brief description of Akashi.
*
@ -1635,13 +1669,20 @@ class AOClient : public QObject
void cmdUnCharCurse(int argc, QStringList argv);
/**
* @brief Forces a client into the charselect screen.
* @brief Forces the caller's client into the charselect screen.
*
* @iscommand
*/
void cmdCharSelect(int argc, QStringList argv);
/**
* @brief Forces the target's client into the charselect screen.
*
* @details The only argument is the **target's ID** whom the client wants to force into charselect.
*
* @iscommand
*/
void cmdCharSelect(int argc, QStringList argv);
void cmdForceCharSelect(int argc, QStringList argv);
/**
* @brief Sends a message to an area that you a CM in.
@ -2042,176 +2083,6 @@ class AOClient : public QObject
*/
bool change_auth_started = false;
/**
* @brief Describes a command's details.
*/
struct CommandInfo
{
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);
};
/**
* @property CommandInfo::action
*
* @brief A function reference that contains what the command actually does.
*
* @param int When called, this parameter will be filled with the argument count. @anchor commandArgc
* @param QStringList When called, this parameter will be filled the list of arguments. @anchor commandArgv
*/
/**
* @brief The list of commands available on the server.
*
* @details Generally called with the format of `/command parameters` in the out-of-character chat.
* @showinitializer
*
* @tparam QString The name of the command, without the leading slash.
* @tparam CommandInfo The details of the command.
* See @ref CommandInfo "the type's documentation" for more details.
*/
const QMap<QString, CommandInfo> commands{
{"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.
*

View File

@ -0,0 +1,197 @@
#ifndef COMMAND_EXTENSION_H
#define COMMAND_EXTENSION_H
#include <QMap>
#include <QObject>
#include <QString>
#include <QVector>
#include "include/acl_roles_handler.h"
class CommandExtension
{
public:
/**
* @brief Constructs a null command extension.
*/
CommandExtension();
/**
* @brief Constructs a command extension with the given command name.
*
* @param f_command_name The command's name.
*/
CommandExtension(QString f_command_name);
/**
* @brief Destroys the command extension.
*/
~CommandExtension();
/**
* @brief Returns the command's name.
*
* @details The command's name act as a possible identifier to determine whatever the command extension matches a command or not.
*/
QString getCommandName() const;
/**
* @brief Sets the command name.
*
* @param f_command_name The command's name.
*/
void setCommandName(QString f_command_name);
/**
* @brief Checks if the given alias matches any of the possible alias of the command extension, including the command's name itself.
*
* @param f_alias The alias to check.
*
* @return True if the alias matches, false otherwise.
*/
bool checkCommandNameAndAlias(QString f_alias) const;
/**
* @brief Returns the aliases of the command.
*/
QStringList getAliases() const;
/**
* @brief Sets the aliases of the command to the given aliases.
*
* @param f_aliases The command aliases.
*/
void setAliases(QStringList f_aliases);
/**
* @brief Returns the list of permissions. If the permissions are not set or empty, returns f_defaultPermissions.
*
* @param f_defaultPermissions A list of permissions to return if the extensions's permissions are not set or empty.
*/
QVector<ACLRole::Permission> getPermissions(QVector<ACLRole::Permission> f_defaultPermissions) const;
/**
* @brief Returns the list of permissions.
*/
QVector<ACLRole::Permission> getPermissions() const;
/**
* @brief Sets the list of permissions to the given list of permissions.
*
* @param f_permissions A list of permissions.
*/
void setPermissions(QVector<ACLRole::Permission> f_permissions);
/**
* @brief Sets the list of permissions based on their captions.
*
* @param f_captions
*
* @see ACLRole#PERMISSION_CAPTIONS
*/
void setPermissionsByCaption(QStringList f_captions);
private:
/**
* @brief The command name to which the extension is loosely associated to.
*/
QString m_command_name;
/**
* @brief A list of aliases for the command.
*/
QStringList m_aliases;
/**
* @brief A list containing both the command's name and the list of aliases.
*/
QStringList m_merged_aliases;
/**
* @brief A list of permissions.
*/
QVector<ACLRole::Permission> m_permissions;
/**
* @brief Updates #m_merged_aliases.
*/
void updateMergedAliases();
};
class CommandExtensionCollection : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructs a null command extension collection.
*
* @details The collection does load extensions automatically.
*
* @param parent Qt-based parent
*/
CommandExtensionCollection(QObject *parent = nullptr);
/**
* @brief Destroys the collection.
*/
~CommandExtensionCollection();
/**
* @brief Sets the command name whitelist to the given list.
*
* @param f_command_names A list of command name.
*
* @see #m_command_name_whitelist
*/
void setCommandNameWhitelist(QStringList f_command_names);
/**
* @brief Returns the list of extensions.
*
* @see CommandExtension
*/
QList<CommandExtension> getExtensions() const;
/**
* @brief Checks if a command extension associated to the given command name exists.
*
* @param f_command_name The target command name.
*
* @return True if the command extension exists, false otherwise.
*/
bool containsExtension(QString f_command_name) const;
/**
* @brief Returns a command extension associated to the given command name. If no command extension is associated to the command name, returns a null command extension.
*
* @param f_command_name The target command name.
*
* @return Returns a command extension.
*/
CommandExtension getExtension(QString f_command_name) const;
/**
* @brief Clear the current command extensions and load command extensions from the given file. The file must be of the INI format.
*
* @details If the command name whitelist is not empty, only command extensions pertaining may be registered.
*
* @param f_filename The path to the file.
*/
bool loadFile(QString f_filename);
private:
/**
* @brief A list of command names to allow.
*
* @see #loadFile
*/
QStringList m_command_name_whitelist;
/**
* @brief A map of extensions associated to a command name.
*/
QMap<QString, CommandExtension> m_extensions;
};
#endif // COMMAND_EXTENSION_H

View File

@ -35,6 +35,7 @@ class ACLRolesHandler;
class Advertiser;
class AOClient;
class AreaData;
class CommandExtensionCollection;
class ConfigManager;
class DBManager;
class Discord;
@ -282,12 +283,20 @@ class Server : public QObject
/**
* @brief Returns a pointer to a database manager.
*
* @return A pointer to database manager.
* @return A pointer to a database manager.
*/
DBManager *getDatabaseManager();
/**
* @brief Returns a pointer to ACL role handler.
*/
ACLRolesHandler *getACLRolesHandler();
/**
* @brief Returns a pointer to a command extension collection.
*/
CommandExtensionCollection *getCommandExtensionCollection();
/**
* @brief The server-wide global timer.
*/
@ -511,8 +520,16 @@ class Server : public QObject
*/
DBManager *db_manager;
/**
* @see ACLRolesHandler
*/
ACLRolesHandler *acl_roles_handler;
/**
* @see CommandExtensionCollection
*/
CommandExtensionCollection *command_extension_collection;
/**
* @brief Connects new AOClient to logger and disconnect handling.
**/

View File

@ -12,7 +12,11 @@ const QHash<QString, ACLRole> ACLRolesHandler::readonly_roles{
{ACLRolesHandler::SUPER_ID, ACLRole(ACLRole::SUPER)},
};
const QHash<ACLRole::Permission, QString> ACLRole::permission_captions{
const QHash<ACLRole::Permission, QString> ACLRole::PERMISSION_CAPTIONS{
{
ACLRole::Permission::NONE,
"none",
},
{
ACLRole::Permission::KICK,
"kick",
@ -31,11 +35,11 @@ const QHash<ACLRole::Permission, QString> ACLRole::permission_captions{
},
{
ACLRole::Permission::CM,
"set_gamemaster",
"gamemaster",
},
{
ACLRole::Permission::GLOBAL_TIMER,
"use_global_timer",
"global_timer",
},
{
ACLRole::Permission::EVI_MOD,
@ -43,7 +47,7 @@ const QHash<ACLRole::Permission, QString> ACLRole::permission_captions{
},
{
ACLRole::Permission::MOTD,
"set_motd",
"motd",
},
{
ACLRole::Permission::ANNOUNCE,
@ -75,7 +79,7 @@ const QHash<ACLRole::Permission, QString> ACLRole::permission_captions{
},
{
ACLRole::Permission::IGNORE_BGLIST,
"ignore_bg_list",
"ignore_background_list",
},
{
ACLRole::Permission::SEND_NOTICE,
@ -176,17 +180,17 @@ bool ACLRolesHandler::loadFile(QString f_file_name)
if (l_settings.status() != QSettings::NoError) {
switch (l_settings.status()) {
case QSettings::AccessError:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: failed to open file; aborting (" << f_file_name << ")";
break;
case QSettings::FormatError:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: file is malformed; aborting (" << f_file_name << ")";
break;
default:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: unknown error; aborting; aborting (" << f_file_name << ")";
break;
}
@ -200,20 +204,23 @@ bool ACLRolesHandler::loadFile(QString f_file_name)
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";
qWarning() << "[ACL Role Handler]"
<< "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.";
qWarning() << "[ACL Role Handler]"
<< "warning: role" << l_upper_group << "already exist";
continue;
}
l_role_records.append(l_upper_group);
ACLRole l_role;
const QList<ACLRole::Permission> l_permissions = ACLRole::permission_captions.keys();
const QList<ACLRole::Permission> 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());
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));
@ -230,17 +237,17 @@ bool ACLRolesHandler::saveFile(QString f_file_name)
if (l_settings.status() != QSettings::NoError) {
switch (l_settings.status()) {
case QSettings::AccessError:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: failed to open file; aborting (" << f_file_name << ")";
break;
case QSettings::FormatError:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: file is malformed; aborting (" << f_file_name << ")";
break;
default:
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: unknown error; aborting; aborting (" << f_file_name << ")";
break;
}
@ -259,22 +266,22 @@ bool ACLRolesHandler::saveFile(QString f_file_name)
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);
l_settings.setValue(ACLRole::PERMISSION_CAPTIONS.value(ACLRole::SUPER), true);
}
else {
const QList<ACLRole::Permission> l_permissions = ACLRole::permission_captions.keys();
const QList<ACLRole::Permission> 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.setValue(ACLRole::PERMISSION_CAPTIONS.value(i_permission), true);
}
}
l_settings.endGroup();
}
l_settings.sync();
if (l_settings.status() != QSettings::NoError) {
qWarning() << "ACLRolesHandler"
qWarning() << "[ACL Role Handler]"
<< "error: failed to write file; aborting (" << f_file_name << ")";
return false;
}

View File

@ -19,10 +19,133 @@
#include "include/aopacket.h"
#include "include/area_data.h"
#include "include/command_extension.h"
#include "include/config_manager.h"
#include "include/db_manager.h"
#include "include/server.h"
const QMap<QString, AOClient::CommandInfo> AOClient::COMMANDS{
{"login", {{ACLRole::NONE}, 0, &AOClient::cmdLogin}},
{"getarea", {{ACLRole::NONE}, 0, &AOClient::cmdGetArea}},
{"getareas", {{ACLRole::NONE}, 0, &AOClient::cmdGetAreas}},
{"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}},
{"lock_background", {{ACLRole::BGLOCK}, 0, &AOClient::cmdBgLock}},
{"unlock_background", {{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}},
{"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}},
{"area_lock", {{ACLRole::CM}, 0, &AOClient::cmdLock}},
{"area_spectate", {{ACLRole::CM}, 0, &AOClient::cmdSpectatable}},
{"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}},
{"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}},
{"set_motd", {{ACLRole::MOTD}, 1, &AOClient::cmdSetMOTD}},
{"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}},
{"notecard_reveal", {{ACLRole::CM}, 0, &AOClient::cmdNoteCardReveal}},
{"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}},
{"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}},
{"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}},
{"ooc_mute", {{ACLRole::MUTE}, 1, &AOClient::cmdOocMute}},
{"ooc_unmute", {{ACLRole::MUTE}, 1, &AOClient::cmdOocUnMute}},
{"block_wtce", {{ACLRole::MUTE}, 1, &AOClient::cmdBlockWtce}},
{"unblock_wtce", {{ACLRole::MUTE}, 1, &AOClient::cmdUnBlockWtce}},
{"block_dj", {{ACLRole::MUTE}, 1, &AOClient::cmdBlockDj}},
{"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}},
{"force_charselect", {{ACLRole::FORCE_CHARSELECT}, 1, &AOClient::cmdForceCharSelect}},
{"togglemusic", {{ACLRole::CM}, 0, &AOClient::cmdToggleMusic}},
{"a", {{ACLRole::NONE}, 2, &AOClient::cmdA}},
{"s", {{ACLRole::NONE}, 0, &AOClient::cmdS}},
{"kick_uid", {{ACLRole::KICK}, 2, &AOClient::cmdKickUid}},
{"firstperson", {{ACLRole::NONE}, 0, &AOClient::cmdFirstPerson}},
{"update_ban", {{ACLRole::BAN}, 3, &AOClient::cmdUpdateBan}},
{"changepass", {{ACLRole::NONE}, 1, &AOClient::cmdChangePassword}},
{"ignore_bglist", {{ACLRole::IGNORE_BGLIST}, 0, &AOClient::cmdIgnoreBgList}},
{"notice", {{ACLRole::SEND_NOTICE}, 1, &AOClient::cmdNotice}},
{"noticeg", {{ACLRole::SEND_NOTICE}, 1, &AOClient::cmdNoticeGlobal}},
{"togglejukebox", {{ACLRole::CM, ACLRole::JUKEBOX}, 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}},
};
void AOClient::clientData()
{
if (last_read + m_socket->bytesAvailable() > 30720) { // Client can send a max of 30KB to the server over two sequential reads
@ -202,20 +325,44 @@ void AOClient::changePosition(QString new_pos)
void AOClient::handleCommand(QString command, int argc, QStringList argv)
{
CommandInfo l_info = commands.value(command, {ACLRole::NONE, -1, &AOClient::cmdDefault});
command = command.toLower();
QString l_target_command = command;
QVector<ACLRole::Permission> l_permissions;
if (!checkPermission(l_info.acl_permission)) {
// check for aliases
const QList<CommandExtension> l_extensions = server->getCommandExtensionCollection()->getExtensions();
for (const CommandExtension &i_extension : l_extensions) {
if (i_extension.checkCommandNameAndAlias(command)) {
l_target_command = i_extension.getCommandName();
l_permissions = i_extension.getPermissions();
break;
}
}
CommandInfo l_command = COMMANDS.value(l_target_command, {{ACLRole::NONE}, -1, &AOClient::cmdDefault});
if (l_permissions.isEmpty()) {
l_permissions.append(l_command.acl_permissions);
}
bool l_has_permissions = false;
for (const ACLRole::Permission i_permission : qAsConst(l_permissions)) {
if (checkPermission(i_permission)) {
l_has_permissions = true;
break;
}
}
if (!l_has_permissions) {
sendServerMessage("You do not have permission to use that command.");
return;
}
if (argc < l_info.minArgs) {
if (argc < l_command.minArgs) {
sendServerMessage("Invalid command syntax.");
sendServerMessage("The expected syntax for this command is: \n" + ConfigManager::commandHelp(command).usage);
return;
}
(this->*(l_info.action))(argc, argv);
(this->*(l_command.action))(argc, argv);
}
void AOClient::arup(ARUPType type, bool broadcast)

View File

@ -0,0 +1,166 @@
#include "include/command_extension.h"
#include <QDebug>
#include <QSettings>
#include "include/akashidefs.h"
CommandExtension::CommandExtension() {}
CommandExtension::CommandExtension(QString f_command_name)
{
setCommandName(f_command_name);
}
CommandExtension::~CommandExtension() {}
QString CommandExtension::getCommandName() const
{
return m_command_name;
}
void CommandExtension::setCommandName(QString f_command_name)
{
m_command_name = f_command_name;
updateMergedAliases();
}
bool CommandExtension::checkCommandNameAndAlias(QString f_alias) const
{
return m_merged_aliases.contains(f_alias, Qt::CaseInsensitive);
}
QStringList CommandExtension::getAliases() const
{
return m_aliases;
}
void CommandExtension::setAliases(QStringList f_aliases)
{
m_aliases = f_aliases;
for (QString &i_alias : m_aliases) {
i_alias = i_alias.toLower();
}
updateMergedAliases();
}
QVector<ACLRole::Permission> CommandExtension::getPermissions(QVector<ACLRole::Permission> f_defaultPermissions) const
{
return m_permissions.isEmpty() ? f_defaultPermissions : m_permissions;
}
QVector<ACLRole::Permission> CommandExtension::getPermissions() const
{
return getPermissions(QVector<ACLRole::Permission>{});
}
void CommandExtension::setPermissions(QVector<ACLRole::Permission> f_permissions)
{
m_permissions = f_permissions;
}
void CommandExtension::setPermissionsByCaption(QStringList f_captions)
{
QVector<ACLRole::Permission> l_permissions;
const QStringList l_permission_captions = ACLRole::PERMISSION_CAPTIONS.values();
for (const QString &i_caption : qAsConst(f_captions)) {
const QString l_lower_caption = i_caption.toLower();
if (!l_permission_captions.contains(l_lower_caption)) {
qWarning() << "[Command Extension]"
<< "error: permission" << i_caption << "does not exist";
continue;
}
l_permissions.append(ACLRole::PERMISSION_CAPTIONS.key(l_lower_caption));
}
setPermissions(l_permissions);
}
void CommandExtension::updateMergedAliases()
{
m_merged_aliases = QStringList{m_command_name} + m_aliases;
}
CommandExtensionCollection::CommandExtensionCollection(QObject *parent) :
QObject(parent)
{}
CommandExtensionCollection::~CommandExtensionCollection() {}
void CommandExtensionCollection::setCommandNameWhitelist(QStringList f_command_names)
{
m_command_name_whitelist = f_command_names;
for (QString &i_alias : m_command_name_whitelist) {
i_alias = i_alias.toLower();
}
}
QList<CommandExtension> CommandExtensionCollection::getExtensions() const
{
return m_extensions.values();
}
bool CommandExtensionCollection::containsExtension(QString f_command_name) const
{
return m_extensions.contains(f_command_name);
}
CommandExtension CommandExtensionCollection::getExtension(QString f_command_name) const
{
return m_extensions.value(f_command_name);
}
bool CommandExtensionCollection::loadFile(QString f_filename)
{
QSettings l_settings(f_filename, QSettings::IniFormat);
l_settings.setIniCodec("UTF-8");
if (l_settings.status() != QSettings::NoError) {
qWarning() << "[Command Extension Collection]"
<< "error: failed to load file" << f_filename << "; aborting";
return false;
}
m_extensions.clear();
QStringList l_alias_records;
QStringList l_command_records;
const QStringList l_group_list = l_settings.childGroups();
for (const QString &i_group : l_group_list) {
const QString l_command_name = i_group.toLower();
if (!m_command_name_whitelist.isEmpty() && !m_command_name_whitelist.contains(l_command_name)) {
qWarning() << "[Command Extension Collection]"
<< "error: command" << l_command_name << "cannot be extended; does not exist";
continue;
}
if (l_command_records.contains(l_command_name)) {
qWarning() << "[Command Extension Collection]"
<< "warning: command extension" << l_command_name << "already exist";
continue;
}
l_command_records.append(l_command_name);
l_settings.beginGroup(i_group);
QStringList l_aliases = l_settings.value("aliases").toString().split(" ", akashi::SkipEmptyParts);
for (QString &i_alias : l_aliases) {
i_alias = i_alias.toLower();
}
for (const QString &i_recorded_alias : l_alias_records) {
if (l_aliases.contains(i_recorded_alias)) {
qWarning() << "[Command Extension Collection]"
<< "warning: command alias" << i_recorded_alias << "was already defined";
l_aliases.removeAll(i_recorded_alias);
}
}
l_alias_records.append(l_aliases);
CommandExtension l_extension(l_command_name);
l_extension.setAliases(l_aliases);
l_extension.setPermissionsByCaption(l_settings.value("permissions").toString().split(" ", akashi::SkipEmptyParts));
m_extensions.insert(l_command_name, std::move(l_extension));
l_settings.endGroup();
}
return true;
}

View File

@ -153,10 +153,10 @@ void AOClient::cmdListPerms(int argc, QStringList argv)
l_message.append("SUPER (Be careful! This grants the user all permissions.)");
}
else {
const QList<ACLRole::Permission> l_permissions = ACLRole::permission_captions.keys();
const QList<ACLRole::Permission> 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));
l_message.append(ACLRole::PERMISSION_CAPTIONS.value(i_permission));
}
}
}

View File

@ -474,34 +474,34 @@ void AOClient::cmdUnCharCurse(int argc, QStringList argv)
void AOClient::cmdCharSelect(int argc, QStringList argv)
{
if (argc == 0) {
changeCharacter(-1);
sendPacket("DONE");
Q_UNUSED(argc);
Q_UNUSED(argv);
changeCharacter(-1);
sendPacket("DONE");
}
void AOClient::cmdForceCharSelect(int argc, QStringList argv)
{
Q_UNUSED(argc);
bool ok = false;
int l_target_id = argv[0].toInt(&ok);
if (!ok) {
sendServerMessage("This ID does not look valid. Please use the client ID.");
return;
}
else {
if (!checkPermission(ACLRole::FORCE_CHARSELECT)) {
sendServerMessage("You do not have permission to force another player to character select!");
return;
}
bool ok = false;
int l_target_id = argv[0].toInt(&ok);
if (!ok) {
sendServerMessage("This ID does not look valid. Please use the client ID.");
return;
}
AOClient *l_target = server->getClientByID(l_target_id);
AOClient *l_target = server->getClientByID(l_target_id);
if (l_target == nullptr) {
sendServerMessage("Unable to locate client with ID " + QString::number(l_target_id) + ".");
return;
}
l_target->changeCharacter(-1);
l_target->sendPacket("DONE");
sendServerMessage("Client has been forced into character select!");
if (l_target == nullptr) {
sendServerMessage("Unable to locate client with ID " + QString::number(l_target_id) + ".");
return;
}
l_target->changeCharacter(-1);
l_target->sendPacket("DONE");
sendServerMessage("Client has been forced into character select!");
}
void AOClient::cmdA(int argc, QStringList argv)

View File

@ -18,6 +18,7 @@
#include "include/aoclient.h"
#include "include/area_data.h"
#include "include/command_extension.h"
#include "include/config_manager.h"
#include "include/db_manager.h"
#include "include/server.h"
@ -165,11 +166,27 @@ void AOClient::cmdCommands(int argc, QStringList argv)
QStringList l_entries;
l_entries << "Allowed commands:";
QMap<QString, CommandInfo>::const_iterator i;
for (i = commands.constBegin(); i != commands.constEnd(); ++i) {
CommandInfo info = i.value();
if (checkPermission(info.acl_permission)) { // if we are allowed to use this command
l_entries << "/" + i.key();
for (i = COMMANDS.constBegin(); i != COMMANDS.constEnd(); ++i) {
const CommandInfo l_command = i.value();
const CommandExtension l_extension = server->getCommandExtensionCollection()->getExtension(i.key());
const QVector<ACLRole::Permission> l_permissions = l_extension.getPermissions(l_command.acl_permissions);
bool l_has_permission = false;
for (const ACLRole::Permission i_permission : qAsConst(l_permissions)) {
if (checkPermission(i_permission)) {
l_has_permission = true;
break;
}
}
if (!l_has_permission) {
continue;
}
QString l_info = "/" + i.key();
const QStringList l_aliases = l_extension.getAliases();
if (!l_aliases.isEmpty()) {
l_info += " [aka: " + l_aliases.join(", ") + "]";
}
l_entries << l_info;
}
sendServerMessage(l_entries.join("\n"));
}
@ -191,19 +208,19 @@ void AOClient::cmdHelp(int argc, QStringList argv)
void AOClient::cmdMOTD(int argc, QStringList argv)
{
if (argc == 0) {
sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n=============");
}
else if (argc > 0) {
if (checkPermission(ACLRole::MOTD)) {
QString l_MOTD = argv.join(" ");
ConfigManager::setMotd(l_MOTD);
sendServerMessage("MOTD has been changed.");
}
else {
sendServerMessage("You do not have permission to change the MOTD");
}
}
Q_UNUSED(argc)
Q_UNUSED(argv)
sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n=============");
}
void AOClient::cmdSetMOTD(int argc, QStringList argv)
{
Q_UNUSED(argc)
QString l_MOTD = argv.join(" ");
ConfigManager::setMotd(l_MOTD);
sendServerMessage("MOTD has been changed.");
}
void AOClient::cmdBans(int argc, QStringList argv)

View File

@ -127,15 +127,10 @@ void AOClient::cmdToggleJukebox(int argc, QStringList argv)
Q_UNUSED(argc);
Q_UNUSED(argv);
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.";
sendServerMessageArea("The jukebox in this area has been " + l_state);
}
else {
sendServerMessage("You do not have permission to change the jukebox status.");
}
AreaData *l_area = server->getAreaById(m_current_area);
l_area->toggleJukebox();
QString l_state = l_area->isjukeboxEnabled() ? "enabled." : "disabled.";
sendServerMessageArea("The jukebox in this area has been " + l_state);
}
void AOClient::cmdAddSong(int argc, QStringList argv)

View File

@ -19,6 +19,7 @@
#include <QQueue>
#include "include/akashidefs.h"
#include "include/aopacket.h"
#include "include/area_data.h"
#include "include/config_manager.h"
@ -258,11 +259,7 @@ void AOClient::pktOocChat(AreaData *area, int argc, QStringList argv, AOPacket p
return;
AOPacket final_packet("CT", {m_ooc_name, l_message, "0"});
if (l_message.at(0) == '/') {
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QStringList l_cmd_argv = l_message.split(" ", QString::SplitBehavior::SkipEmptyParts);
#else
QStringList l_cmd_argv = l_message.split(" ", Qt::SkipEmptyParts);
#endif
QStringList l_cmd_argv = l_message.split(" ", akashi::SkipEmptyParts);
QString l_command = l_cmd_argv[0].trimmed().toLower();
l_command = l_command.right(l_command.length() - 1);
l_cmd_argv.removeFirst();

View File

@ -22,6 +22,7 @@
#include "include/aoclient.h"
#include "include/aopacket.h"
#include "include/area_data.h"
#include "include/command_extension.h"
#include "include/config_manager.h"
#include "include/db_manager.h"
#include "include/discord.h"
@ -48,6 +49,10 @@ Server::Server(int p_port, int p_ws_port, QObject *parent) :
acl_roles_handler = new ACLRolesHandler;
acl_roles_handler->loadFile("config/acl_roles.ini");
command_extension_collection = new CommandExtensionCollection;
command_extension_collection->setCommandNameWhitelist(AOClient::COMMANDS.keys());
command_extension_collection->loadFile("config/command_extensions.ini");
// We create it, even if its not used later on.
discord = new Discord(this);
@ -286,6 +291,8 @@ void Server::reloadSettings()
handleDiscordIntegration();
logger->loadLogtext();
m_ipban_list = ConfigManager::iprangeBans();
acl_roles_handler->loadFile("config/acl_roles.ini");
command_extension_collection->loadFile("config/command_extensions.ini");
}
void Server::broadcast(AOPacket packet, int area_index)
@ -463,6 +470,11 @@ ACLRolesHandler *Server::getACLRolesHandler()
return acl_roles_handler;
}
CommandExtensionCollection *Server::getCommandExtensionCollection()
{
return command_extension_collection;
}
void Server::allowMessage()
{
m_can_send_ic_messages = true;

View File

@ -3,4 +3,5 @@ TEMPLATE = subdirs
SUBDIRS += \
unittest_area \
unittest_music_manager \
unittest_acl_roles_handler
unittest_acl_roles_handler \
unittest_command_extension

View File

@ -0,0 +1,237 @@
#include <QDebug>
#include <QRegularExpression>
#include <QTest>
#include <include/command_extension.h>
namespace tests {
namespace unittests {
/**
* @brief Unit Tester class for ACL roles-related functions.
*/
class tst_CommandExtension : public QObject
{
Q_OBJECT
public:
typedef QVector<ACLRole::Permission> PermVector;
CommandExtension m_extension;
private slots:
/**
* @brief Initialises every tests
*/
void init();
/**
* @brief The data function of checkCommandName
*/
void checkCommandName_data();
/**
* @brief Tests various command names
*/
void checkCommandName();
/**
* @brief The data function of checkAliases
*/
void checkAliases_data();
/**
* @brief Tests various aliases
*/
void checkAliases();
/**
* @brief The data function of checkAlias
*/
void checkAlias_data();
/**
* @brief checkAlias
*/
void checkAlias();
/**
* @brief The data function of checkPermission
*/
void checkPermission_data();
/**
* @brief Tests various permission scenarios
*/
void checkPermission();
/**
* @brief The data function of setPermissionsByCaption
*/
void setPermissionsByCaption_data();
/**
* @brief Tests the role caption conversion
*/
void setPermissionsByCaption();
};
void tst_CommandExtension::init()
{
m_extension = CommandExtension();
}
void tst_CommandExtension::checkCommandName_data()
{
QTest::addColumn<QString>("name");
QTest::addColumn<QString>("expected_name");
QTest::addColumn<bool>("expected_result");
QTest::newRow("Identical name") << "extension"
<< "extension" << true;
QTest::newRow("Different name") << "different"
<< "extension" << false;
QTest::newRow("No name") << QString{}
<< "extension" << false;
}
void tst_CommandExtension::checkCommandName()
{
QFETCH(QString, name);
QFETCH(QString, expected_name);
QFETCH(bool, expected_result);
{
CommandExtension l_extension(name);
QCOMPARE(l_extension.getCommandName() == expected_name, expected_result);
}
{
CommandExtension l_extension;
l_extension.setCommandName(name);
QCOMPARE(l_extension.getCommandName() == expected_name, expected_result);
}
}
void tst_CommandExtension::checkAliases_data()
{
QTest::addColumn<QString>("name");
QTest::addColumn<QStringList>("aliases");
QTest::addColumn<QStringList>("expected_aliases");
QTest::addColumn<bool>("expected_result");
QTest::newRow("Identical aliases") << "extension" << QStringList{"ext", "extended"} << QStringList{"ext", "extended"} << true;
QTest::newRow("Different aliases") << "extension" << QStringList{"ext", "extended"} << QStringList{"will", "not", "be", "valid"} << false;
}
void tst_CommandExtension::checkAliases()
{
QFETCH(QString, name);
QFETCH(QStringList, aliases);
QFETCH(QStringList, expected_aliases);
QFETCH(bool, expected_result);
{
CommandExtension l_extension;
l_extension.setAliases(aliases);
QCOMPARE(l_extension.getAliases() == expected_aliases, expected_result);
}
{
CommandExtension l_extension(name);
l_extension.setAliases(aliases);
QCOMPARE(l_extension.getAliases() == expected_aliases, expected_result);
}
{
CommandExtension l_extension;
l_extension.setCommandName(name);
l_extension.setAliases(aliases);
QCOMPARE(l_extension.getAliases() == expected_aliases, expected_result);
}
}
void tst_CommandExtension::checkAlias_data()
{
QTest::addColumn<QString>("name");
QTest::addColumn<QStringList>("aliases");
QTest::addColumn<QString>("target");
QTest::addColumn<bool>("expected_result");
QTest::newRow("Target found: name") << "extension" << QStringList{"ext", "extended"} << "extension" << true;
QTest::newRow("Target found: alias") << "extension" << QStringList{"ext", "extended"} << "ext" << true;
QTest::newRow("Target not found") << "extension" << QStringList{"ext", "extended"} << "wont_find_me" << false;
}
void tst_CommandExtension::checkAlias()
{
QFETCH(QString, name);
QFETCH(QStringList, aliases);
QFETCH(QString, target);
QFETCH(bool, expected_result);
{
m_extension.setCommandName(name);
m_extension.setAliases(aliases);
QCOMPARE(m_extension.checkCommandNameAndAlias(target), expected_result);
}
}
void tst_CommandExtension::setPermissionsByCaption_data()
{
QTest::addColumn<QStringList>("permission_captions");
QTest::addColumn<PermVector>("expected_permissions");
QTest::addColumn<bool>("message_required");
QTest::addColumn<bool>("expected_result");
QTest::addRow("Valid captions") << QStringList{"none", "super"} << PermVector{ACLRole::NONE, ACLRole::SUPER} << false << true;
QTest::addRow("Invalid captions") << QStringList{"none", "not_none"} << PermVector{ACLRole::NONE, ACLRole::SUPER} << true << false;
QTest::addRow("Valid and invalid captions") << QStringList{"none", "not_super"} << PermVector{ACLRole::NONE} << true << true;
}
void tst_CommandExtension::setPermissionsByCaption()
{
QFETCH(QStringList, permission_captions);
QFETCH(PermVector, expected_permissions);
QFETCH(bool, message_required);
QFETCH(bool, expected_result);
{
if (message_required) {
QTest::ignoreMessage(QtWarningMsg, QRegularExpression("\\[Command Extension\\] error: permission \".*?\" does not exist"));
}
m_extension.setPermissionsByCaption(permission_captions);
QCOMPARE(m_extension.getPermissions() == expected_permissions, expected_result);
}
}
void tst_CommandExtension::checkPermission_data()
{
QTest::addColumn<PermVector>("permissions");
QTest::addColumn<PermVector>("default_permissions");
QTest::addColumn<PermVector>("expected_permissions");
QTest::addColumn<bool>("expected_result");
QTest::addRow("Matches permissions") << PermVector{ACLRole::SUPER} << PermVector{} << PermVector{ACLRole::SUPER} << true;
QTest::addRow("Matches default permissions") << PermVector{} << PermVector{ACLRole::NONE} << PermVector{ACLRole::NONE} << true;
}
void tst_CommandExtension::checkPermission()
{
QFETCH(PermVector, permissions);
QFETCH(PermVector, default_permissions);
QFETCH(PermVector, expected_permissions);
QFETCH(bool, expected_result);
{
m_extension.setPermissions(permissions);
QCOMPARE(m_extension.getPermissions(default_permissions) == expected_permissions, expected_result);
}
}
}
}
QTEST_APPLESS_MAIN(tests::unittests::tst_CommandExtension)
#include "tst_unittest_command_extension.moc"

View File

@ -0,0 +1,6 @@
QT -= gui
include(../tests_common.pri)
SOURCES += \
tst_unittest_command_extension.cpp