Added the action logging system

This commit is contained in:
WolverinDEV 2020-06-28 14:01:14 +02:00
parent 8e16309930
commit 68cfab1ac9
22 changed files with 2988 additions and 86 deletions

View File

@ -141,6 +141,9 @@ set(SERVER_SOURCE_FILES
src/snapshots/groups.cpp
src/snapshots/deploy.cpp
src/manager/ActionLogger.cpp
src/manager/ActionLoggerImpl.cpp
src/manager/ConversationManager.cpp
src/client/SpeakingClientHandshake.cpp
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)

View File

@ -26,6 +26,7 @@
#endif
#include <unistd.h>
#include <files/FileServer.h>
#include "./manager/ActionLogger.h"
#undef _POSIX_SOURCE
@ -49,6 +50,13 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
this->dbHelper = new DatabaseHelper(this->getSql());
this->action_logger_ = std::make_unique<log::ActionLogger>();
if(!this->action_logger_->initialize(error_message)) {
logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message);
logCritical(LOG_INSTANCE, "Action log has been disabled.");
this->action_logger_->finalize();
}
this->_properties = new Properties();
this->_properties->register_property_type<property::InstanceProperties>();
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;

View File

@ -27,6 +27,10 @@ namespace ts {
class FileServerHandler;
}
namespace log {
class ActionLogger;
}
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@ -51,6 +55,7 @@ namespace ts {
BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
log::ActionLogger* action_logger() { return &*this->action_logger_; }
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
@ -120,7 +125,7 @@ namespace ts {
BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
file::FileServerHandler* file_server_handler_{nullptr};
std::unique_ptr<log::ActionLogger> action_logger_{nullptr};
ts::Properties* _properties = nullptr;

View File

@ -6,6 +6,7 @@
#include <misc/timer.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
#include "InstanceHandler.h"
using namespace std;
@ -323,7 +324,7 @@ void VirtualServer::notify_client_kick(
*
* Note: channel cant be a ref because the channel itself gets deleted!
*/
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock, bool temp_delete) {
if(!tree_lock.owns_lock())
tree_lock.lock();
if(channel->deleted)
@ -358,7 +359,12 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
tree_lock.lock(); /* no clients left within that tree */
command_locks.clear();
auto channel_ids = this->channelTree->delete_channel_root(channel);
auto deleted_channels = this->channelTree->delete_channel_root(channel);
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
for(const auto& deleted_channel : deleted_channels) {
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
}
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
unique_lock client_channel_lock(client->channel_lock);
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);

View File

@ -187,7 +187,7 @@ void VirtualServer::executeServerTick() {
if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay
if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay
this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock);
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
if(channel_lock.owns_lock())
channel_lock.unlock();
}

View File

@ -270,7 +270,8 @@ namespace ts {
std::shared_ptr<ServerChannel> /* target channel */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* kick message */,
std::unique_lock<std::shared_mutex>& /* tree lock */
std::unique_lock<std::shared_mutex>& /* tree lock */,
bool temporary_auto_delete
);
void send_text_message(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */);

View File

@ -416,13 +416,13 @@ namespace ts {
command_result handleCommandChannelDelPerm(Command&);
//Server group manager management
command_result handleCommandServerGroupCopy(Command&);
command_result handleCommandServerGroupAdd(Command&);
command_result handleCommandServerGroupCopy(Command&);
command_result handleCommandServerGroupRename(Command&);
command_result handleCommandServerGroupDel(Command&);
command_result handleCommandServerGroupClientList(Command&);
command_result handleCommandServerGroupDelClient(Command&);
command_result handleCommandServerGroupAddClient(Command&);
command_result handleCommandServerGroupDelClient(Command&);
command_result handleCommandServerGroupPermList(Command&);
command_result handleCommandServerGroupAddPerm(Command&);
command_result handleCommandServerGroupDelPerm(Command&);
@ -598,6 +598,8 @@ namespace ts {
command_result handleCommandLogView(Command&);
//CMD_TODO handleCommandLogAdd
command_result handleCommandLogQuery(Command&);
command_result handleCommandLogAdd(Command&);
command_result handleCommandListFeatureSupport(Command &cmd);

View File

@ -11,6 +11,7 @@
#include "src/InstanceHandler.h"
#include "StringVariable.h"
#include "misc/timer.h"
#include "../manager/ActionLogger.h"
using namespace std::chrono;
using namespace ts;
@ -800,11 +801,15 @@ void SpeakingClient::processJoin() {
}
}
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name());
}
void SpeakingClient::processLeave() {
auto ownLock = _this.lock();
auto server = this->getServer();
auto channel = this->currentChannel;
if(server){
logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()));
{

View File

@ -6,10 +6,12 @@
#include <PermissionManager.h>
#include <src/client/ConnectedClient.h>
#include "./helpers.h"
#include "../../manager/ActionLogger.h"
namespace ts::command::bulk_parser {
template <bool kParseValue>
class PermissionBulkParser {
friend class PermissionBulkParser<false>;
public:
explicit PermissionBulkParser(ts::ParameterBulk& bulk) {
if(bulk.has("permid")) {
@ -45,7 +47,7 @@ namespace ts::command::bulk_parser {
}
}
PermissionBulkParser(const PermissionBulkParser&) = delete;
PermissionBulkParser(PermissionBulkParser&&) = default;
PermissionBulkParser(PermissionBulkParser&&) noexcept = default;
~PermissionBulkParser() {
this->error_.release_data();
@ -78,9 +80,9 @@ namespace ts::command::bulk_parser {
inline void apply_to(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode) const {
if(this->is_grant_permission()) {
manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
this->old_value = manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
manager->set_permission(
this->old_value = manager->set_permission(
this->permission_type(),
{ this->value(), 0 },
mode,
@ -91,11 +93,61 @@ namespace ts::command::bulk_parser {
}
}
inline void log_update(
server::log::PermissionActionLogger& logger,
ServerId sid,
const std::shared_ptr<server::ConnectedClient>& issuer,
server::log::PermissionTarget target,
permission::v2::PermissionUpdateType mode,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name
) const {
if(this->is_grant_permission()) {
switch (mode) {
case permission::v2::delete_value:
if(!this->old_value.flags.grant_set)
return;
logger.log_permission_remove_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant);
break;
case permission::v2::set_value:
if(this->old_value.flags.grant_set) {
logger.log_permission_edit_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant, this->value_);
} else {
logger.log_permission_add_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->value_);
}
break;
case permission::v2::do_nothing:
break;
}
} else {
switch (mode) {
case permission::v2::delete_value:
if(!this->old_value.flags.value_set)
return;
logger.log_permission_remove_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
break;
case permission::v2::set_value:
if(this->old_value.flags.value_set) {
logger.log_permission_edit_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip, this->value_, this->flag_negated_, this->flag_skip_);
} else {
logger.log_permission_add_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
}
break;
case permission::v2::do_nothing:
break;
}
}
}
inline void apply_to_channel(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const {
if(this->is_grant_permission()) {
manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
this->old_value = manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
manager->set_channel_permission(
this->old_value = manager->set_channel_permission(
this->permission_type(),
channel_id,
{ this->value(), true },
@ -122,6 +174,7 @@ namespace ts::command::bulk_parser {
permission::PermissionValue value_{0};
ts::command_result error_{error::ok};
mutable permission::v2::PermissionContainer old_value{};
#ifndef NDEBUG
bool error_released_{false};
#endif
@ -131,7 +184,7 @@ namespace ts::command::bulk_parser {
class PermissionBulksParser {
public:
PermissionBulksParser(const PermissionBulksParser&) = delete;
PermissionBulksParser(PermissionBulksParser&&) = default;
PermissionBulksParser(PermissionBulksParser&&) noexcept = default;
template <typename base_iterator>
struct FilteredPermissionIterator : public base_iterator {

View File

@ -15,6 +15,7 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include <cstdint>
#include "helpers.h"
@ -138,11 +139,30 @@ command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1);
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_NORMAL && !this->server) return command_result{error::parameter_invalid, "You cant create normal channel groups on the template server"};
if(cmd["name"].string().empty()) return command_result{error::parameter_invalid, "invalid group name"};
log::GroupType log_group_type;
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
log_group_type = log::GroupType::TEMPLATE;
} else {
if(!this->server)
return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
log_group_type = log::GroupType::NORMAL;
}
if(cmd["name"].string().empty())
return command_result{error::parameter_invalid, "invalid group name"};
for(const auto& gr : group_manager->availableServerGroups(true))
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "Group already exists"};
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL)
return command_result{error::parameter_invalid, "Group already exists"};
auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as<GroupType>(), cmd["name"].string());
serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, group->groupId(), group->name(), 0, "");
if (group) {
group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing);
if(this->server)
@ -212,6 +232,28 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
if(!group_manager->copyGroupPermissions(source_group, target_group))
return command_result{error::vs_critical, "failed to copy group permissions"};
log::GroupType log_group_type;
switch (target_group->type()) {
case GroupType::GROUP_TYPE_QUERY:
log_group_type = log::GroupType::QUERY;
break;
case GroupType::GROUP_TYPE_TEMPLATE:
log_group_type = log::GroupType::TEMPLATE;
break;
case GroupType::GROUP_TYPE_NORMAL:
log_group_type = log::GroupType::NORMAL;
break;
default:
return command_result{error::parameter_invalid, "type"};
}
serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name());
global_update = !this->server || !group_manager->isLocalGroup(target_group);
} else {
//Copy a new group
@ -223,6 +265,24 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
return command_result{result};
}
log::GroupType log_group_type;
switch (target_type) {
case GroupType::GROUP_TYPE_QUERY:
log_group_type = log::GroupType::QUERY;
break;
case GroupType::GROUP_TYPE_TEMPLATE:
log_group_type = log::GroupType::TEMPLATE;
break;
case GroupType::GROUP_TYPE_NORMAL:
log_group_type = log::GroupType::NORMAL;
break;
default:
return command_result{error::parameter_invalid, "type"};
}
if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL)
return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"};
@ -232,6 +292,8 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
auto target_group_id = group_manager->copyGroup(source_group, target_type, cmd["name"], target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId());
if(target_group_id == 0)
return command_result{error::vs_critical, "failed to copy group"};
serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group_id, cmd["name"], source_group->groupId(), source_group->name());
if(this->getType() == ClientType::CLIENT_QUERY) {
Command notify("");
@ -256,10 +318,26 @@ command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) {
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
auto channel_group = group_manager->findGroup(cmd["cgid"].as<GroupId>());
if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL)
return command_result{error::parameter_invalid, "invalid channel group id"};
ACTION_REQUIRES_GROUP_PERMISSION(channel_group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
auto type = channel_group->type();
log::GroupType log_group_type;
if(type == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
log_group_type = log::GroupType::TEMPLATE;
} else {
log_group_type = log::GroupType::NORMAL;
}
auto old_name = channel_group->name();
group_manager->renameGroup(channel_group, cmd["name"].string());
serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, channel_group->groupId(), channel_group->name(), old_name);
if(this->server)
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyChannelGroupList();
@ -285,21 +363,37 @@ command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) {
}
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->groupId())
return command_result{error::parameter_invalid, "Could not delete instance default channel group!"};
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->groupId())
return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"};
auto type = channel_group->type();
log::GroupType log_group_type;
if(type == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
log_group_type = log::GroupType::TEMPLATE;
} else {
log_group_type = log::GroupType::NORMAL;
}
if (!cmd["force"].as<bool>())
if (!group_manager->listGroupMembers(channel_group, false).empty())
return command_result{error::database_empty_result, "group not empty!"};
if (group_manager->deleteGroup(channel_group) && this->server) {
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
if(cl->update_cached_permissions()) /* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
}
cl->notifyChannelGroupList();
});
if (group_manager->deleteGroup(channel_group)) {
serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, channel_group->groupId(), channel_group->name());
if(this->server) {
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
if(cl->update_cached_permissions()) /* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
}
cl->notifyChannelGroupList();
});
}
}
return command_result{error::ok};
@ -414,6 +508,15 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
bool updateList{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CHANNEL_GROUP,
permission::v2::PermissionUpdateType::set_value,
0, "",
channelGroup->groupId(), channelGroup->name()
);
updateList |= ppermission.is_group_property();
}
@ -453,6 +556,14 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
bool updateList{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CHANNEL_GROUP,
permission::v2::PermissionUpdateType::delete_value,
0, "",
channelGroup->groupId(), channelGroup->name()
);
updateList |= ppermission.is_group_property();
}
@ -685,24 +796,50 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
);
}
for (auto &prop : cmd[0].keys()) {
if (prop == "channel_flag_default") continue;
if (prop == "channel_order") continue;
if (prop == "channel_name") continue;
if (prop == "cpid") continue;
if (prop == "cid") continue;
/* log channel create */
{
log::ChannelType log_channel_type;
switch (created_channel->channelType()) {
case ChannelType::permanent:
log_channel_type = log::ChannelType::PERMANENT;
break;
const auto &property = property::find<property::ChannelProperties>(prop);
case ChannelType::semipermanent:
log_channel_type = log::ChannelType::SEMI_PERMANENT;
break;
case ChannelType::temporary:
log_channel_type = log::ChannelType::TEMPORARY;
break;
}
serverInstance->action_logger()->channel_logger.log_channel_create(this->getServerId(), this->ref(), created_channel->channelId(), log_channel_type);
}
for (auto &property_name : cmd[0].keys()) {
if (property_name == "channel_flag_default") continue;
if (property_name == "channel_order") continue;
if (property_name == "channel_name") continue;
if (property_name == "cpid") continue;
if (property_name == "cid") continue;
const auto &property = property::find<property::ChannelProperties>(property_name);
if(property == property::CHANNEL_UNDEFINED) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop);
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + property_name);
continue;
}
if(!property.validate_input(cmd[prop].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + std::string{property.name} + "')");
if(!property.validate_input(cmd[property_name].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[property_name].as<string>() + "', Property: '" + std::string{property.name} + "')");
continue;
}
created_channel->properties()[property] = cmd[prop].as<std::string>();
auto prop = created_channel->properties()[property];
auto old_value = prop.value();
auto new_value = cmd[property_name].as<std::string>();
if(old_value == new_value)
continue;
prop = new_value;
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), created_channel->channelId(), property, old_value, new_value);
}
if(created_channel->parent()) {
if(created_channel->parent()->channelType() > created_channel->channelType())
@ -738,6 +875,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!");
channel_admin_group = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
}
/* FIXME: Log group assignment */
this->server->groups->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel);
if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB))
@ -776,11 +915,15 @@ command_result ConnectedClient::handleCommandChannelDelete(Command &cmd) {
if(!clients.empty())
ACTION_REQUIRES_PERMISSION(permission::b_channel_delete_flag_force, 1, channel->channelId());
this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock);
this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock, false);
} else {
auto channel_ids = channel_tree->deleteChannelRoot(channel);
this->notifyChannelDeleted(channel_ids, this->ref());
auto deleted_channel_ids = channel_tree->deleteChannelRoot(channel);
for(const auto& channelId : deleted_channel_ids) {
serverInstance->action_logger()->channel_logger.log_channel_delete(0, this->ref(), channelId, channel->channelId() == channelId ? log::ChannelDeleteReason::USER_ACTION : log::ChannelDeleteReason::PARENT_DELETED);
}
this->notifyChannelDeleted(deleted_channel_ids, this->ref());
}
return command_result{error::ok};
}
@ -1108,9 +1251,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
for(const property::PropertyDescription* key : keys) {
if(*key == property::CHANNEL_ORDER) {
/* TODO: May move that up because if it fails may some other props have already be applied */
auto old_channel_order = channel->channelOrder();
if (!channel_tree->change_order(channel, cmd[std::string{key->name}]))
return command_result{error::channel_invalid_order, "Can't change order id"};
auto channel_parent = channel->parent() ? channel->parent()->channelId() : 0;
serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(),
channel_parent, channel_parent,
old_channel_order, channel->channelOrder());
if(this->server) {
auto parent = channel->hasParent() ? channel_tree->findLinkedChannel(channel->parent()->channelId()) : nullptr;
auto previous = channel_tree->findLinkedChannel(channel->previousChannelId());
@ -1144,6 +1293,9 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
}
});
}
/* property has already been updated as well the log has also been written */
continue;
} else if(*key == property::CHANNEL_FLAG_DEFAULT) {
old_default_channel = channel_tree->getDefaultChannel();
if(old_default_channel == channel) {
@ -1216,7 +1368,14 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
}
channel->properties()[*key] = cmd[std::string{key->name}].string();
auto prop = channel->properties()[*key];
auto old_value = prop.value();
auto new_value = cmd[std::string{key->name}].string();
if(old_value == new_value)
continue;
prop = new_value;
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), channel->channelId(), *key, old_value, new_value);
}
if(this->server) {
vector<property::ChannelProperties> key_vector;
@ -1283,9 +1442,16 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) {
if(channel->parent() == parent && channel->channelOrder() == (order ? order->channelId() : 0))
return command_result{error::ok};
if (channel->parent() != parent)
auto old_parent_channel_id = channel->parent() ? channel->parent()->channelId() : 0;
auto old_channel_order = channel->channelOrder();
bool change_parent{channel->parent() != parent};
bool change_order{(order ? order->channelId() : 0) != channel->channelOrder()};
if (change_parent)
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_parent, 1, channel_id);
if ((order ? order->channelId() : 0) != channel->channelOrder())
if (change_order)
ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_sortorder, 1, channel_id);
{
@ -1331,6 +1497,24 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) {
} while ((current_channel = dynamic_pointer_cast<ServerChannel>(current_channel->parent())));
}
/* log all the updates */
serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(),
old_parent_channel_id, channel->parent() ? channel->parent()->channelId() : 0,
old_channel_order, channel->channelOrder());
for(const auto& type_update : channel_type_updates) {
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(),
property::describe(property::CHANNEL_FLAG_PERMANENT),
"",
type_update->properties()[property::CHANNEL_FLAG_PERMANENT].value()
);
serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(),
property::describe(property::CHANNEL_FLAG_SEMI_PERMANENT),
"",
type_update->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].value()
);
}
if(this->server) {
auto self_rev = this->ref();
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
@ -1442,8 +1626,17 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) {
auto permission_manager = channel->permissions();
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
auto channelId = channel->channelId();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CHANNEL,
permission::v2::PermissionUpdateType::set_value,
channelId, channel->name(),
0, ""
);
updateClients |= ppermission.is_client_view_property();
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
@ -1480,8 +1673,17 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
auto permission_manager = channel->permissions();
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
auto channelId = channel->channelId();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CHANNEL,
permission::v2::PermissionUpdateType::delete_value,
channelId, channel->name(),
0, ""
);
updateClients |= ppermission.is_client_view_property();
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
@ -1586,6 +1788,14 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
bool update_view{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT_CHANNEL,
permission::v2::PermissionUpdateType::delete_value,
cldbid, "",
channel->channelId(), channel->name()
);
update_view |= ppermission.is_client_view_property();
}
@ -1646,6 +1856,14 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
bool update_view{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId());
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT_CHANNEL,
permission::v2::PermissionUpdateType::set_value,
cldbid, "",
channel->channelId(), channel->name()
);
update_view |= ppermission.is_client_view_property();
}

View File

@ -15,6 +15,7 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include <cstdint>
#include "helpers.h"
@ -77,6 +78,7 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
result.emplace_result(error::client_invalid_id);
continue;
}
if (client->getType() == CLIENT_MUSIC) {
result.emplace_result(error::client_invalid_type);
continue;
@ -101,11 +103,16 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
}
for(auto& client : clients) {
auto old_channel = client->getChannel();
if(!old_channel) continue;
if (target_channel) {
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), target_channel);
serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), target_channel->channelId(), target_channel->name(), old_channel->channelId(), old_channel->name());
} else {
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), nullptr);
client->close_connection(system_clock::now() + seconds(1));
serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), 0, "", old_channel->channelId(), old_channel->name());
}
}
@ -257,6 +264,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
true,
server_channel_w_lock
);
serverInstance->action_logger()->client_channel_logger.log_client_move(this->getServerId(), this->ref(), client->ref(), channel->channelId(), channel->name(), oldChannel->channelId(), oldChannel->name());
if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr<BasicChannel>& channel) { return &*channel == &*oldChannel; }) == channels.end())
channels.push_back(oldChannel);
}
@ -266,7 +276,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
server_channel_w_lock.lock();
if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as<int64_t>() == 0)
if(this->server->getClientsByChannelRoot(oldChannel, false).empty())
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock);
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock, true);
if(server_channel_w_lock.owns_lock())
server_channel_w_lock.unlock();
}
@ -701,8 +711,24 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as<bool>() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
}
client->properties()[key.first] = cmd[0][key.second].value();
auto property = client->properties()[key.first];
auto old_value = property.value();
auto new_value = cmd[0][key.second].value();
if(old_value == new_value)
continue;
property = new_value;
updates.push_back(key.first);
serverInstance->action_logger()->client_edit_logger.log_client_edit(
this->getServerId(),
this->ref(),
client,
*key.first,
old_value,
new_value
);
}
if(update_talk_rights)
client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]);
@ -954,6 +980,15 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
bool update_channels{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT,
permission::v2::PermissionUpdateType::set_value,
cldbid, "",
0, ""
);
update_channels |= ppermission.is_client_view_property();
}
@ -989,6 +1024,14 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
bool update_channels{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT,
permission::v2::PermissionUpdateType::delete_value,
cldbid, "",
0, ""
);
update_channels |= ppermission.is_client_view_property();
}

View File

@ -19,6 +19,7 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include "helpers.h"
#include <files/FileServer.h>
@ -185,6 +186,7 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) {
if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered};
auto& file_system = file::server()->file_system();
std::shared_lock channel_tree_lock{this->server->channel_tree_lock};
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel)
return command_result{error::channel_invalid_id};
@ -194,6 +196,7 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) {
if(!channel->permission_granted(permission::i_ft_needed_directory_create_power, this->calculate_permission(permission::i_ft_directory_create_power, channel->channelId()), true))
return command_result{permission::i_ft_directory_create_power};
channel_tree_lock.unlock();
auto create_result = file_system.create_channel_directory(virtual_file_server, channel->channelId(), cmd["dirname"].string());
if(!create_result->wait_for(kFileAPITimeout))
@ -217,6 +220,8 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) {
}
}
serverInstance->action_logger()->file_logger.log_file_directory_create(this->getServerId(), this->ref(), channel->channelId(), cmd["dirname"].string());
return command_result{error::ok};
}
@ -234,9 +239,12 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
ts::command_result_bulk response{};
response.emplace_result_n(cmd.bulkCount(), error::ok);
std::vector<std::tuple<uint64_t, std::string>> file_log_info{};
auto file_path = cmd["path"].string();
std::shared_ptr<file::ExecuteResponse<file::filesystem::FileDeleteError, file::filesystem::FileDeleteResponse>> delete_response{};
if (cmd[0].has("cid") && cmd["cid"] != 0) {
std::shared_lock channel_tree_lock{this->server->channel_tree_lock};
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel)
return command_result{error::channel_invalid_id};
@ -249,8 +257,11 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
std::vector<std::string> delete_files{};
delete_files.reserve(cmd.bulkCount());
for(size_t index{0}; index < cmd.bulkCount(); index++)
file_log_info.reserve(cmd.bulkCount());
for(size_t index{0}; index < cmd.bulkCount(); index++) {
delete_files.push_back(file_path + "/" + cmd[index]["name"].string());
file_log_info.emplace_back(channel->channelId(), file_path + "/" + cmd[index]["name"].string());
}
delete_response = file_system.delete_channel_files(virtual_file_server, channel->channelId(), delete_files);
} else {
@ -261,6 +272,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
std::vector<std::string> delete_files{};
delete_files.reserve(cmd.bulkCount());
file_log_info.reserve(cmd.bulkCount());
for(size_t index{0}; index < cmd.bulkCount(); index++) {
auto file_name = cmd[index]["name"].string();
if(!file_name.starts_with("/icon_")) {
@ -269,6 +281,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
}
delete_files.push_back(file_name);
file_log_info.emplace_back(0, file_name);
}
delete_response = file_system.delete_icons(virtual_file_server, delete_files);
@ -281,6 +294,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
std::vector<std::string> delete_files{};
delete_files.reserve(cmd.bulkCount());
file_log_info.reserve(cmd.bulkCount());
for(size_t index{0}; index < cmd.bulkCount(); index++) {
auto file_name = cmd[index]["name"].string();
if(!file_name.starts_with("/avatar_")) {
@ -311,9 +325,12 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
}
delete_files.push_back("/avatar_" + avId);
file_log_info.emplace_back(0, "/avatar_" + avId);
} else {
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
this->server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_FLAG_AVATAR});
delete_files.push_back("/avatar_" + this->getAvatarId());
file_log_info.emplace_back(0, "/avatar_" + this->getAvatarId());
}
}
@ -341,13 +358,17 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
const auto& file_status = delete_response->response();
size_t bulk_index{0};
for(const auto& file : file_status.delete_results) {
for(size_t index{0}; index < file_status.delete_results.size(); index++) {
const auto& file = file_status.delete_results[index];
const auto& log_file_info = file_log_info[index];
while(response.response(bulk_index).error_code() != error::ok)
bulk_index++;
using Status = file::filesystem::FileDeleteResponse::StatusType;
switch (file.status) {
case Status::SUCCESS:
serverInstance->action_logger()->file_logger.log_file_delete(this->getServerId(), this->ref(), std::get<0>(log_file_info), std::get<1>(log_file_info));
/* we already emplaced success */
break;
@ -491,8 +512,7 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) {
auto avId = hex::hex(base64::decode(uid), 'a', 'q');
delete_files.push_back("/avatar_" + avId);
} else {
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
this->server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_FLAG_AVATAR});
delete_files.push_back("/avatar_" + this->getAvatarId());
}
}
@ -641,8 +661,9 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) {
return command_result{error::file_transfer_client_quota_exceeded};
}
ChannelId log_channel_id{0};
if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
std::shared_lock channel_tree_lock{this->server->channel_tree_lock};
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel)
return command_result{error::channel_invalid_id, "Cant resolve channel"};
@ -652,6 +673,7 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) {
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_upload_power, permission::i_ft_file_upload_power, true);
transfer_response = file::server()->file_transfer().initialize_channel_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, virtual_file_server, channel->channelId(), info);
log_channel_id = channel->channelId();
} else {
if (cmd["path"].string().empty() && cmd["name"].string().starts_with("/icon_")) {
auto max_size = this->calculate_permission(permission::i_max_icon_filesize, 0);
@ -727,6 +749,7 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) {
result.put_unchecked(0, "proto", "1");
this->sendCommand(result);
serverInstance->action_logger()->file_logger.log_file_upload(this->getServerId(), this->ref(), log_channel_id, info.file_path);
return command_result{error::ok};
}
@ -781,7 +804,9 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) {
info.client_id = this->getClientId();
info.max_concurrent_transfers = kMaxClientTransfers;
ChannelId log_channel_id{0};
if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
std::shared_lock channel_tree_lock{this->server->channel_tree_lock};
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel)
return command_result{error::channel_invalid_id};
@ -791,6 +816,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) {
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_download_power, permission::i_ft_file_download_power, true);
transfer_response = file::server()->file_transfer().initialize_channel_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, virtual_file_server, channel->channelId(), info);
log_channel_id = channel->channelId();
} else {
if (cmd["path"].as<std::string>().empty() && cmd["name"].string().starts_with("/icon_")) {
transfer_response = file::server()->file_transfer().initialize_icon_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, virtual_file_server, info);
@ -859,6 +885,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) {
result.put_unchecked(0, "seekpos", transfer->file_offset);
this->sendCommand(result);
serverInstance->action_logger()->file_logger.log_file_download(this->getServerId(), this->ref(), log_channel_id, info.file_path);
return command_result{error::ok};
}
@ -881,6 +908,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) {
auto channel_id = cmd["cid"].as<ChannelId>();
auto target_channel_id = cmd[0].has("tcid") ? cmd["tcid"].as<ChannelId>() : channel_id;
std::shared_lock channel_tree_lock{this->server->channel_tree_lock};
auto channel = this->server->channelTree->findChannel(channel_id);
if (!channel)
return command_result{error::channel_invalid_id};
@ -900,6 +928,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) {
ACTION_REQUIRES_CHANNEL_PERMISSION(targetChannel, permission::i_ft_needed_file_rename_power, permission::i_ft_file_rename_power, true);
}
channel_tree_lock.unlock();
auto rename_response = file::server()->file_system().rename_channel_file(virtual_file_server, channel_id, cmd["oldname"].string(), target_channel_id, cmd["newname"].string());
@ -933,6 +962,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) {
}
}
serverInstance->action_logger()->file_logger.log_file_rename(this->getServerId(), this->ref(), channel_id, cmd["oldname"].string(), target_channel_id, cmd["newname"].string());
return command_result{error::ok};
}

View File

@ -23,6 +23,7 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
@ -211,6 +212,9 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
else if (command == "help") return this->handleCommandHelp(cmd);
else if (command == "logview") return this->handleCommandLogView(cmd);
else if (command == "logquery") return this->handleCommandLogQuery(cmd);
else if (command == "logadd") return this->handleCommandLogAdd(cmd);
else if (command == "servergroupautoaddperm") return this->handleCommandServerGroupAutoAddPerm(cmd);
else if (command == "servergroupautodelperm") return this->handleCommandServerGroupAutoDelPerm(cmd);
@ -448,23 +452,28 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
}
}
std::shared_ptr<GroupAssignment> old_group;
{
auto old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false);
old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false);
if(old_group) {
auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id);
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) {
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) {
if(target_cldbid != this->getClientDatabaseId())
return command_result{permission::i_channel_group_member_remove_power};
auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id);
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true))
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true))
return command_result{permission::i_channel_group_self_remove_power};
}
}
}
this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel);
std::shared_ptr<ConnectedClient> connected_client{};
for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) {
connected_client = targetClient;
unique_lock client_channel_lock_w(targetClient->channel_lock);
auto updates = this->server->groups->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */
client_channel_lock_w.unlock();
@ -487,6 +496,21 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
targetClient->updateChannelClientProperties(false, true);
}
if(old_group) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL,
old_group->group->groupId(), old_group->group->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
if(serverGroup != this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL)) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL,
serverGroup->groupId(), serverGroup->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
return command_result{error::ok};
}
@ -684,8 +708,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
bool banned = false;
if(existing) {
if(existing->invokerDbId == this->getClientDatabaseId()) {
if(existing->until == until) return command_result{error::database_duplicate_entry};
else {
if(existing->until == until) {
return command_result{error::database_duplicate_entry};
} else {
existing->until = until;
serverInstance->banManager()->updateBan(existing);
banned = true;
@ -694,7 +719,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
serverInstance->banManager()->unban(existing);
}
}
if(!banned) serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until);
if(!banned) {
serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until);
}
for(auto server : (this->server ? std::deque<shared_ptr<VirtualServer>>{this->server} : serverInstance->getVoiceServerManager()->serverInstances()))
server->testBanStateChange(_this.lock());
@ -1934,19 +1961,21 @@ command_result ConnectedClient::handleCommandPermReset(ts::Command& cmd) {
command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy");
string log_path;
ServerId target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
string server_identifier;
if(target_server > 0)
server_identifier = to_string(target_server);
else server_identifier = "[A-Z]{0,7}";
if(target_server == 0)
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
else
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy");
#if 0
string log_path;
string server_identifier;
if(target_server > 0)
server_identifier = to_string(target_server);
else
server_identifier = "[A-Z]{0,7}";
for(const auto& sink : logger::logger(target_server)->sinks()) {
if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)) {
log_path = dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)->filename();
@ -2077,7 +2106,127 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
}
}
this->sendCommand(result);
#else
constexpr static std::array<std::string_view, 5> log_output{
"The command 'logview' is not supported anymore.",
"In order to lookup the server actions use 'logquery'.",
"",
"If you need to lookup the TeaSpeak - Server logs, please visit the 'logs/' folder,",
"located at your TeaSpeak installation folder. All logs could be found there."
};
command_builder result{this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""};
size_t index{0};
if(lagacy) {
for(const auto& message : log_output) {
std::string line{"2020-06-27 00:00.0000|CRITICAL|Server Instance | |"};
line += message;
result.put_unchecked(index++, "l", line);
}
} else {
for(const auto& message : log_output) {
std::string line{"[2020-06-27 00:00:00][ERROR] "};
line += message;
result.put_unchecked(index++, "l", line);
}
}
this->sendCommand(result);
#endif
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandLogQuery(ts::Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
uint64_t target_server = (cmd[0].has("instance") && cmd["instance"].as<bool>()) || cmd.hasParm("instance") ? (ServerId) 0 : this->getServerId();
if(target_server == 0) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
}
std::chrono::system_clock::time_point timestamp_begin{}, timestamp_end{};
if(cmd[0].has("begin"))
timestamp_begin += std::chrono::milliseconds{cmd["begin"].as<uint64_t>()};
if(cmd[0].has("end"))
timestamp_end += std::chrono::milliseconds{cmd["end"].as<uint64_t>()};
if(timestamp_begin <= timestamp_end && timestamp_begin.time_since_epoch().count() != 0)
return command_result{error::parameter_constraint_violation, "begin > end"};
size_t limit{100};
if(cmd[0].has("limit"))
limit = std::min((size_t) 2000, cmd["limit"].as<size_t>());
std::vector<log::LoggerGroup> groups{};
if(cmd[0].has("groups")) {
auto groups_string = cmd["groups"].string();
size_t offset{0}, findex;
do {
findex = groups_string.find(',', offset);
auto group_name = groups_string.substr(offset, findex - offset);
offset = findex + 1;
size_t index{0};
for(; index < (size_t) log::LoggerGroup::MAX; index++) {
auto group = static_cast<log::LoggerGroup>(index);
if(log::kLoggerGroupName[(int) group] == group_name) {
if(std::find(groups.begin(), groups.end(), group) != groups.end())
return command_result{error::parameter_invalid, "groups"};
groups.push_back(group);
}
}
if(index == (size_t) log::LoggerGroup::MAX)
return command_result{error::parameter_invalid, "groups"};
} while(offset != 0);
}
auto result = serverInstance->action_logger()->query(groups, target_server, timestamp_begin, timestamp_end, limit);
if(result.empty())
return command_result{error::database_empty_result};
command_builder notify{this->notify_response_command("notifylogquery")};
size_t index{0};
size_t threshold = this->getType() == ClientType::CLIENT_QUERY ? (size_t) -1 : 4096; /* limit each command to 4096 bytes */
for(log::LogEntryInfo& entry : result) {
notify.set_bulk(index, std::move(entry.info));
if(index == 0)
notify.put_unchecked(0, "sid", this->getServerId());
if(notify.current_size() > threshold) {
this->sendCommand(notify);
notify.reset();
index = 0;
}
index++;
}
if(index> 0)
this->sendCommand(notify);
if(this->getType() != ClientType::CLIENT_QUERY)
this->sendCommand(command_builder{"notifylogqueryfinished"});
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandLogAdd(ts::Command& cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
uint64_t target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
if(target_server == 0) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_add, 1);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_add, 1);
}
serverInstance->action_logger()->custom_logger.add_log_message(target_server, this->ref(), cmd["msg"]);
return command_result{error::ok};
}

View File

@ -561,8 +561,17 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions())
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST,
permission::v2::PermissionUpdateType::set_value,
playlist->playlist_id(), "",
0, ""
);
}
return pparser.build_command_result();
}
@ -582,8 +591,17 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions())
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST,
permission::v2::PermissionUpdateType::delete_value,
playlist->playlist_id(), "",
0, ""
);
}
return pparser.build_command_result();
}
@ -705,8 +723,17 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions())
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST_CLIENT,
permission::v2::PermissionUpdateType::set_value,
playlist->playlist_id(), "",
client_id, ""
);
}
return pparser.build_command_result();
}
@ -730,8 +757,17 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions())
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST_CLIENT,
permission::v2::PermissionUpdateType::delete_value,
playlist->playlist_id(), "",
client_id, ""
);
}
return pparser.build_command_result();
}

View File

@ -19,6 +19,7 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include "helpers.h"
#include "./bulk_parsers.h"
@ -67,6 +68,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!target_server && cmd["sid"].as<ServerId>() != 0) return command_result{error::server_invalid_id};
}
ServerId serverId = target_server ? target_server->serverId : 0;
auto cache = make_shared<CalculateCache>();
map<string, string> toApplay;
@ -220,10 +222,13 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')");
continue;
}
if(target_server)
target_server->properties()[info] = elm.second;
else
(*serverInstance->getDefaultServerProperties())[info] = elm.second;
auto property = target_server ? target_server->properties()[info] : (*serverInstance->getDefaultServerProperties())[info];
if(property.value() == elm.second)
continue;
auto old_value = property.value();
property = elm.second;
serverInstance->action_logger()->server_edit_logger.log_server_edit(serverId, this->ref(), info, old_value, elm.second);
keys.push_back(elm.first);
group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
@ -301,23 +306,33 @@ command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) {
if(cmd["name"].string().empty()) return command_result{error::parameter_invalid};
log::GroupType log_group_type;
if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(cmd["type"].as<GroupType>() == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
} else if(!this->server) return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
log_group_type = log::GroupType::TEMPLATE;
} else {
if(!this->server)
return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"};
log_group_type = log::GroupType::NORMAL;
}
auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get();
for(const auto& gr : group_manager->availableServerGroups(true))
if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) return command_result{error::parameter_invalid, "Group already exists"};
auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as<GroupType>(), cmd["name"].string());
if (group) {
group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing);
if(this->server)
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
} else return command_result{error::vs_critical};
if(!group)
return command_result{error::vs_critical};
group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing);
if(this->server) {
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
}
serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, group->groupId(), group->name(), 0, "");
return command_result{error::ok};
}
@ -379,6 +394,28 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
if(!group_manager->copyGroupPermissions(source_group, target_group))
return command_result{error::vs_critical, "failed to copy group permissions"};
log::GroupType log_group_type;
switch (target_group->type()) {
case GroupType::GROUP_TYPE_QUERY:
log_group_type = log::GroupType::QUERY;
break;
case GroupType::GROUP_TYPE_TEMPLATE:
log_group_type = log::GroupType::TEMPLATE;
break;
case GroupType::GROUP_TYPE_NORMAL:
log_group_type = log::GroupType::NORMAL;
break;
default:
return command_result{error::parameter_invalid, "type"};
}
serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
this->ref(), log::GroupTarget::SERVER, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name());
global_update = !this->server || !group_manager->isLocalGroup(target_group);
} else {
//Copy a new group
@ -389,6 +426,23 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
if(result != permission::undefined)
return command_result{result};
}
log::GroupType log_group_type;
switch (target_type) {
case GroupType::GROUP_TYPE_QUERY:
log_group_type = log::GroupType::QUERY;
break;
case GroupType::GROUP_TYPE_TEMPLATE:
log_group_type = log::GroupType::TEMPLATE;
break;
case GroupType::GROUP_TYPE_NORMAL:
log_group_type = log::GroupType::NORMAL;
break;
default:
return command_result{error::parameter_invalid, "type"};
}
if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL)
return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"};
@ -400,6 +454,8 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) {
if(target_group_id == 0)
return command_result{error::vs_critical, "failed to copy group"};
serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(),
this->ref(), log::GroupTarget::SERVER, log_group_type, target_group_id, cmd["name"].string(), source_group->groupId(), source_group->name());
if(this->getType() == ClientType::CLIENT_QUERY) {
Command notify("");
notify["sgid"] = target_group_id;
@ -428,13 +484,20 @@ command_result ConnectedClient::handleCommandServerGroupRename(Command &cmd) {
ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true);
auto type = serverGroup->type();
log::GroupType log_group_type;
if(type == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
log_group_type = log::GroupType::TEMPLATE;
} else {
log_group_type = log::GroupType::NORMAL;
}
auto old_name = serverGroup->name();
group_manager->renameGroup(serverGroup, cmd["name"].string());
serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name(), old_name);
if(this->server)
this->server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
@ -456,18 +519,26 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) {
if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId())
return command_result{error::parameter_invalid, "Could not delete default server group!"};
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId())
return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"};
if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId())
return command_result{error::parameter_invalid, "Could not delete instance default server group!"};
if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId())
return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"};
auto type = serverGroup->type();
log::GroupType log_group_type;
if(type == GroupType::GROUP_TYPE_QUERY) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1);
log_group_type = log::GroupType::QUERY;
} else if(type == GroupType::GROUP_TYPE_TEMPLATE) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
log_group_type = log::GroupType::TEMPLATE;
} else {
log_group_type = log::GroupType::NORMAL;
}
if (!cmd["force"].as<bool>())
@ -475,6 +546,7 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) {
return command_result{error::database_empty_result, "group not empty!"};
if (group_manager->deleteGroup(serverGroup)) {
serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name());
if(this->server)
this->server->forEachClient([&](shared_ptr<ConnectedClient> cl) {
if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) {
@ -613,8 +685,10 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd)
}
}
std::shared_ptr<ConnectedClient> connected_client{};
for(const auto& _server : target_server ? std::deque<shared_ptr<VirtualServer>>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) {
for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) {
connected_client = targetClient;
if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) {
for (const auto &client : _server->getClients()) {
if(client->isClientVisible(targetClient, true) || client == targetClient)
@ -627,6 +701,13 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd)
}
}
}
for(const auto& group : applied_groups) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(target_server ? target_server->getServerId() : 0,
this->ref(), log::GroupTarget::SERVER,
group->groupId(), group->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
return command_result{error::ok};
}
@ -740,8 +821,10 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
}
}
std::shared_ptr<ConnectedClient> connected_client{};
for(const auto& _server : target_server ? std::deque<shared_ptr<VirtualServer>>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) {
for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) {
connected_client = targetClient;
ConnectedLockedClient clock{targetClient};
if(!clock) continue;
@ -757,6 +840,13 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
}
}
}
for(const auto& group : applied_groups) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(target_server ? target_server->getServerId() : 0,
this->ref(), log::GroupTarget::SERVER,
group->groupId(), group->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
return command_result{error::ok};
}
@ -803,6 +893,15 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
auto permissions = serverGroup->permissions();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::SERVER_GROUP,
permission::v2::PermissionUpdateType::set_value,
serverGroup->groupId(), serverGroup->name(),
0, ""
);
update_server_group_list |= ppermission.is_group_property();
update_talk_power |= ppermission.is_client_view_property();
}
@ -859,6 +958,15 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
bool update_talk_power{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::SERVER_GROUP,
permission::v2::PermissionUpdateType::delete_value,
serverGroup->groupId(), serverGroup->name(),
0, ""
);
update_server_group_list |= ppermission.is_group_property();
update_talk_power |= ppermission.is_client_view_property();
}
@ -923,8 +1031,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
bool update_clients{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
for(const auto& serverGroup : groups)
for(const auto& serverGroup : groups) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::SERVER_GROUP,
permission::v2::PermissionUpdateType::set_value,
serverGroup->groupId(), serverGroup->name(),
0, ""
);
}
update_server_group_list |= ppermission.is_group_property();
update_clients |= ppermission.is_client_view_property();
@ -993,8 +1110,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
bool update_clients{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
for(const auto& serverGroup : groups)
for(const auto& serverGroup : groups) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::SERVER_GROUP,
permission::v2::PermissionUpdateType::delete_value,
serverGroup->groupId(), serverGroup->name(),
0, ""
);
}
update_server_group_list |= ppermission.is_group_property();
update_clients |= ppermission.is_client_view_property();

View File

@ -11,6 +11,7 @@
#include <src/ShutdownHelper.h>
#include <ThreadPool/Timer.h>
#include <numeric>
#include "src/manager/ActionLogger.h"
#include "src/client/command_handler/helpers.h"
@ -154,8 +155,10 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
{
threads::MutexLock lock(this->handle->loginLock);
if(!account)
if(!account) {
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER);
return command_result{error::client_invalid_password, "username or password dose not match"};
}
if (account->password != password) {
if(!this->whitelisted) {
@ -168,6 +171,8 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
return command_result{error::ban_flooding};
}
}
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::INVALID_PASSWORD);
return command_result{error::client_invalid_password, "username or password dose not match"};
}
}
@ -242,12 +247,13 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
this->properties()[property::CLIENT_TOTALCONNECTIONS]++;
this->updateChannelClientProperties(true, true);
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::SUCCESS);
return command_result{error::ok};
}
command_result QueryClient::handleCommandLogout(Command &) {
CMD_RESET_IDLE;
if(this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) return command_result{error::client_not_logged_in, "not logged in"};
if(this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) return command_result{error::client_not_logged_in};
this->properties()[property::CLIENT_LOGIN_NAME] = "";
this->query_account = nullptr;
@ -293,6 +299,7 @@ command_result QueryClient::handleCommandLogout(Command &) {
}
this->updateChannelClientProperties(true, true);
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), "", log::QueryAuthenticateResult::SUCCESS);
return command_result{error::ok};
}
@ -315,6 +322,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) {
if(target && target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) return command_result{error::server_is_not_running};
if(target == this->server) return command_result{error::ok};
auto old_server_id = this->getServerId();
if(target) {
if(this->query_account && this->query_account->bound_server > 0) {
if(target->getServerId() != this->query_account->bound_server)
@ -362,7 +370,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) {
this->update_cached_permissions();
}
this->updateChannelClientProperties(true, true);
serverInstance->action_logger()->query_logger.log_query_switch(std::dynamic_pointer_cast<QueryClient>(this->ref()), this->properties()[property::CLIENT_LOGIN_NAME].value(), old_server_id, this->getServerId());
return command_result{error::ok};
}
@ -632,6 +640,7 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
time_global = duration_cast<milliseconds>(end - start);
}
serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION);
Command res("");
res["sid"] = server->getServerId();
@ -656,6 +665,7 @@ command_result QueryClient::handleCommandServerDelete(Command& cmd) {
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return command_result{error::server_invalid_id, "invalid bounded server"};
if(!serverInstance->getVoiceServerManager()->deleteServer(server)) return command_result{error::vs_critical};
serverInstance->action_logger()->server_logger.log_server_delete(server->getServerId(), this->ref());
return command_result{error::ok};
}
@ -684,7 +694,9 @@ command_result QueryClient::handleCommandServerStart(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_start_any, 1);
string err;
if(!server->start(err)) return command_result{error::vs_critical, err};
if(!server->start(err))
return command_result{error::vs_critical, err};
serverInstance->action_logger()->server_logger.log_server_start(server->getServerId(), this->ref());
return command_result{error::ok};
}
@ -712,6 +724,7 @@ command_result QueryClient::handleCommandServerStop(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_stop_any, 1);
server->stop("server stopped", false);
serverInstance->action_logger()->server_logger.log_server_stop(server->getServerId(), this->ref());
return command_result{error::ok};
}
@ -846,7 +859,8 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
auto str = cmd.build().substr(strlen("serversnapshotdeploy "));
if(!ignore_hash) {
auto buildHash = base64::encode(digest::sha1(str));
if(buildHash != hash) return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
if(buildHash != hash)
return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
}
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
@ -874,6 +888,7 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
res["error_start"] = "";
res["started"] = true;
}
serverInstance->action_logger()->server_logger.log_server_create(result->getServerId(), this->ref(), log::ServerCreateReason::SNAPSHOT_DEPLOY);
}
res["time"] = duration_cast<milliseconds>(end - start).count();

View File

@ -10,6 +10,8 @@
#include "VoiceClient.h"
#include "src/VirtualServer.h"
#include "../../server/VoiceServer.h"
#include "src/InstanceHandler.h"
#include "src/manager/ActionLogger.h"
using namespace std;
using namespace std::chrono;
@ -146,6 +148,11 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas
cmd["invokeruid"] = invoker->getUid();
}
auto old_channel = this->currentChannel;
if(old_channel) {
serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name());
}
if(notify_viewer && this->server) {
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock);

View File

@ -11,6 +11,7 @@
#include <src/client/ConnectedClient.h>
#include <misc/std_unique_ptr.h>
#include <src/client/SpeakingClient.h>
#include "../../manager/ActionLogger.h"
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
@ -637,6 +638,10 @@ void WebClient::handleMessage(const std::string &message) {
//candidate:0 1 UDP 2122252543 192.168.43.141 34590 typ host
bool WebClient::disconnect(const std::string &reason) {
auto old_channel = this->currentChannel;
if(old_channel) {
serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name());
}
{
unique_lock server_channel_lock(this->server->channel_tree_lock);
{

View File

@ -0,0 +1,449 @@
//
// Created by WolverinDEV on 26/06/2020.
//
#include "ActionLogger.h"
#include "../client/ConnectedClient.h"
#include <sql/sqlite/SqliteSQL.h>
#include <log/LogUtils.h>
using namespace ts::server;
using namespace ts::server::log;
Invoker log::kInvokerSystem{"System", "system", 0};
void AbstractActionLogger::do_log(ServerId sid, Action action, sql::command &command) const {
if(!this->enabled_) return;
command.executeLater().waitAndGetLater([sid, action](const sql::result& result) {
if(!result) {
logError(sid, "Failed to log action {}: {}", kActionName[(int) action], result.fmtStr());
}
}, sql::result{});
}
template <>
void TypedActionLogger<>::compile_model(sql::model &result, const std::string &command, const ActionLogger *logger) {
std::exchange(result, sql::model{logger->sql_manager(), command});
}
template <>
void TypedActionLogger<>::register_invoker(ActionLogger *logger, const Invoker &invoker) {
logger->register_invoker(invoker);
}
template <>
std::string TypedActionLogger<>::generate_insert(const std::string &table_name,
const FixedHeaderKeys& header,
DatabaseColumn *payload_columns,
size_t payload_columns_length) {
std::string result{"INSERT INTO `"};
result.reserve(256);
result += table_name + "` (";
result += "`" + header.timestamp.name + "`,";
result += "`" + header.server_id.name + "`,";
result += "`" + header.invoker_id.name + "`,";
result += "`" + header.invoker_name.name + "`,";
result += "`" + header.action.name + "`";
for(size_t index{0}; index < payload_columns_length; index++)
result += ",`" + payload_columns[index].name + "`";
result += ") VALUES (:h0, :h1, :h2, :h3, :h4";
for(size_t index{0}; index < payload_columns_length; index++)
result += ", " + TypedActionLogger::value_binding_name(index);
result += ");";
debugMessage(0, "Insert query: {}", result);
return result;
}
template <>
sql::command TypedActionLogger<>::compile_query(
ActionLogger *logger,
uint64_t server_id,
const std::chrono::system_clock::time_point &begin_timestamp,
const std::chrono::system_clock::time_point &end_timestamp, size_t limit,
const std::string& table_name,
const FixedHeaderKeys& header,
DatabaseColumn *payload_columns,
size_t payload_columns_length) {
std::string command{"SELECT "};
command.reserve(256);
command += "`" + header.timestamp.name + "`,";
command += "`" + header.server_id.name + "`,";
command += "`" + header.invoker_id.name + "`,";
command += "`" + header.invoker_name.name + "`,";
command += "`" + header.action.name + "`";
for(size_t index{0}; index < payload_columns_length; index++)
command += ",`" + payload_columns[index].name + "`";
command += " FROM `" + table_name + "` WHERE `" + header.server_id.name + "` = " + std::to_string(server_id);
if(begin_timestamp.time_since_epoch().count() > 0)
command += " AND `" + header.timestamp.name + "` <= " + std::to_string(std::chrono::ceil<std::chrono::milliseconds>(begin_timestamp.time_since_epoch()).count());
if(begin_timestamp.time_since_epoch().count() > 0)
command += " AND `" + header.timestamp.name + "` >= " + std::to_string(std::chrono::floor<std::chrono::milliseconds>(end_timestamp.time_since_epoch()).count());
command += " ORDER BY `timestamp` DESC";
if(limit > 0)
command += " LIMIT " + std::to_string(limit);
command += ";";
debugMessage(0, "Log query: {}", command);
return sql::command{logger->sql_manager(), command};
}
template <>
bool TypedActionLogger<>::create_table(
std::string& error,
ActionLogger *logger,
const std::string& table_name,
const FixedHeaderKeys& header,
DatabaseColumn *payload_columns,
size_t payload_columns_length) {
std::string command{};
/* ATTENTION: If I implement MySQL add "CHARACTER SET=utf8" AT THE END! */
if(logger->sql_manager()->getType() != sql::TYPE_SQLITE) {
error = "unsupported database type";
return false;
}
command.reserve(256);
command += "CREATE TABLE `" + table_name + "` (";
command += "`id` INTEGER PRIMARY KEY NOT NULL,";
command += "`" + header.timestamp.name + "` " + header.timestamp.type + ",";
command += "`" + header.server_id.name + "` " + header.server_id.type + ",";
command += "`" + header.invoker_id.name + "` " + header.invoker_id.type + ",";
command += "`" + header.invoker_name.name + "` " + header.invoker_name.type + ",";
command += "`" + header.action.name + "` " + header.action.type;
for(size_t index{0}; index < payload_columns_length; index++)
command += ",`" + payload_columns[index].name + "` " + payload_columns[index].type;
command += ");";
auto result = sql::command{logger->sql_manager(), command}.execute();
if(!result) {
error = result.fmtStr();
return false;
}
return true;
}
template <>
bool TypedActionLogger<>::parse_fixed_header(FixedHeaderValues &result, std::string *values, size_t values_length) {
if(values_length< 5)
return false;
int64_t timestamp;
try {
timestamp = std::stoll(values[0]);
result.server_id = std::stoull(values[1]);
result.invoker.database_id = std::stoull(values[2]);
} catch (std::exception&) {
return false;
}
result.timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{timestamp};
result.invoker.name = values[3];
result.action = Action::UNKNOWN;
for(size_t index{0}; index < (size_t) Action::MAX; index++) {
auto action = static_cast<Action>(index);
if(kActionName[index] == values[4]) {
result.action = action;
}
}
return result.action != Action::UNKNOWN;
}
template <>
void TypedActionLogger<>::bind_fixed_header(sql::command &command, const FixedHeaderValues &fhvalues) {
command.value(":h0", std::chrono::floor<std::chrono::milliseconds>(fhvalues.timestamp).time_since_epoch().count());
command.value(":h1", fhvalues.server_id);
command.value(":h2", fhvalues.invoker.database_id);
command.value(":h3", fhvalues.invoker.name);
command.value(":h4", kActionName[(int) fhvalues.action]);
}
ActionLogger::ActionLogger() = default;
ActionLogger::~ActionLogger() {
this->finalize();
}
#define CURRENT_VERSION 1
bool ActionLogger::initialize(std::string &error) {
int db_version{0};
this->sql_handle = new sql::sqlite::SqliteManager{};
auto result = this->sql_handle->connect("sqlite://InstanceLogs.sqlite");
if(!result) {
error = result.fmtStr();
goto error_exit;
}
{
if(sql_handle->getType() == sql::TYPE_MYSQL) {
sql::command(this->sql_handle, "SET NAMES utf8").execute();
//sql::command(this->manager, "DEFAULT CHARSET=utf8").execute();
} else if(sql_handle->getType() == sql::TYPE_SQLITE) {
sql::command(this->sql_handle, "PRAGMA locking_mode = EXCLUSIVE;").execute();
sql::command(this->sql_handle, "PRAGMA synchronous = NORMAL;").execute();
sql::command(this->sql_handle, "PRAGMA journal_mode = WAL;").execute();
sql::command(this->sql_handle, "PRAGMA encoding = \"UTF-8\";").execute();
}
}
/* begin transaction, if available */
if(this->sql_handle->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql_handle, "BEGIN TRANSACTION;").execute();
if(!result) {
error = "failed to begin transaction (" + result.fmtStr() + ")";
goto error_exit;
}
}
{
result = sql::command{this->sql_handle, "CREATE TABLE IF NOT EXISTS general(`key` VARCHAR(256), `value` TEXT);"}.execute();
if(!result) {
error = "failed to create if not exists the general table: " + result.fmtStr();
goto error_exit;
}
sql::command{this->sql_handle, "SELECT `value` FROM `general` WHERE `key` = 'version';"}.query([&](int, std::string* values, std::string*) {
db_version = std::stoi(values[0]);
});
}
switch (db_version) {
case 0:
/* initial setup */
case CURRENT_VERSION:
/* current version */
break;
default:
error = CURRENT_VERSION < db_version ? "database version is newer than supported (supported: " + std::to_string(CURRENT_VERSION) + ", database: " + std::to_string(db_version) + ")" :
"invalid database version (" + std::to_string(db_version) + ")";
}
if(!this->server_logger.setup(db_version, error)) {
error = "server logger: " + error;
goto error_exit;
}
if(!this->server_edit_logger.setup(db_version, error)) {
error = "server edit logger: " + error;
goto error_exit;
}
if(!this->channel_logger.setup(db_version, error)) {
error = "channel logger: " + error;
goto error_exit;
}
if(!this->permission_logger.setup(db_version, error)) {
error = "permission logger: " + error;
goto error_exit;
}
if(!this->group_logger.setup(db_version, error)) {
error = "group logger: " + error;
goto error_exit;
}
if(!this->group_assignment_logger.setup(db_version, error)) {
error = "group assignment logger: " + error;
goto error_exit;
}
if(!this->client_channel_logger.setup(db_version, error)) {
error = "client channel logger: " + error;
goto error_exit;
}
if(!this->client_edit_logger.setup(db_version, error)) {
error = "client edit logger: " + error;
goto error_exit;
}
if(!this->file_logger.setup(db_version, error)) {
error = "file logger: " + error;
goto error_exit;
}
if(!this->custom_logger.setup(db_version, error)) {
error = "custom logger: " + error;
goto error_exit;
}
if(!this->query_logger.setup(db_version, error)) {
error = "query logger: " + error;
goto error_exit;
}
if(!this->query_authenticate_logger.setup(db_version, error)) {
error = "query authenticate logger: " + error;
goto error_exit;
}
/* update the version */
{
std::string command{};
if(db_version == 0)
command = "INSERT INTO `general` (`key`, `value`) VALUES ('version', :version);";
else
command = "UPDATE `general` SET `value` = :version WHERE `key` = 'version';";
auto res = sql::command{this->sql_handle, command, variable{":version", CURRENT_VERSION}}.execute();
if(!res) {
logCritical(LOG_INSTANCE, "Failed to increment action log database version!");
goto error_exit;
}
}
if(this->sql_handle->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql_handle, "COMMIT;").execute();
if(!result) {
error = "failed to commit changes";
return false;
}
}
/* enable all loggers */
this->server_logger.set_enabled(true);
this->server_edit_logger.set_enabled(true);
this->channel_logger.set_enabled(true);
this->permission_logger.set_enabled(true);
this->group_logger.set_enabled(true);
this->group_assignment_logger.set_enabled(true);
this->client_channel_logger.set_enabled(true);
this->client_edit_logger.set_enabled(true);
this->file_logger.set_enabled(true);
this->custom_logger.set_enabled(true);
this->query_logger.set_enabled(true);
this->query_authenticate_logger.set_enabled(true);
return true;
error_exit:
if(this->sql_handle && this->sql_handle->connected()) {
result = sql::command(this->sql_handle, "ROLLBACK;").execute();
if (!result) {
logCritical(LOG_GENERAL, "Failed to rollback log database after transaction.");
} else {
debugMessage(LOG_GENERAL, "Rollbacked log database successfully.");
}
}
this->finalize();
return false;
}
void ActionLogger::finalize() {
if(!this->sql_handle)
return;
this->server_logger.finalize();
this->server_edit_logger.finalize();
this->channel_logger.finalize();
this->permission_logger.finalize();
this->group_logger.finalize();
this->group_assignment_logger.finalize();
this->client_channel_logger.finalize();
this->client_edit_logger.finalize();
this->custom_logger.finalize();
this->file_logger.finalize();
this->query_logger.finalize();
this->query_authenticate_logger.finalize();
this->sql_handle->pool->threads()->wait_for(std::chrono::seconds{1});
this->sql_handle->disconnect();
delete this->sql_handle;
this->sql_handle = nullptr;
}
void ActionLogger::register_invoker(const Invoker &) {}
std::vector<LogEntryInfo> ActionLogger::query(
std::vector<LoggerGroup> groups,
uint64_t server,
const std::chrono::system_clock::time_point &begin,
const std::chrono::system_clock::time_point &end,
size_t limit) {
std::vector<LogEntryInfo> result{};
result.reserve(limit == 0 ? 1024 * 8 : limit * 2);
std::vector<std::deque<LogEntryInfo>> intresult{};
intresult.reserve(8);
for(size_t index{0}; index < (size_t) LoggerGroup::MAX; index++) {
auto group = static_cast<LoggerGroup>(index);
if(!groups.empty() && std::find(groups.begin(), groups.end(), group) == groups.end())
continue;
intresult.clear();
switch (group) {
case LoggerGroup::SERVER:
std::exchange(intresult.emplace_back(), this->server_logger.query(server, begin, end, limit));
std::exchange(intresult.emplace_back(), this->server_edit_logger.query(server, begin, end, limit));
std::exchange(intresult.emplace_back(), this->group_logger.query(server, begin, end, limit));
std::exchange(intresult.emplace_back(), this->group_assignment_logger.query(server, begin, end, limit));
break;
case LoggerGroup::CHANNEL:
std::exchange(intresult.emplace_back(), this->channel_logger.query(server, begin, end, limit));
break;
case LoggerGroup::CLIENT:
std::exchange(intresult.emplace_back(), this->client_channel_logger.query(server, begin, end, limit));
std::exchange(intresult.emplace_back(), this->client_edit_logger.query(server, begin, end, limit));
break;
case LoggerGroup::FILES:
std::exchange(intresult.emplace_back(), this->file_logger.query(server, begin, end, limit));
break;
case LoggerGroup::PERMISSION:
std::exchange(intresult.emplace_back(), this->permission_logger.query(server, begin, end, limit));
break;
case LoggerGroup::CUSTOM:
std::exchange(intresult.emplace_back(), this->custom_logger.query(server, begin, end, limit));
break;
case LoggerGroup::QUERY:
std::exchange(intresult.emplace_back(), this->query_logger.query(server, begin, end, limit));
std::exchange(intresult.emplace_back(), this->query_authenticate_logger.query(server, begin, end, limit));
break;
case LoggerGroup::MAX:
default:
assert(false);
break;
}
for(auto& qresult : intresult) {
if(qresult.empty())
continue;
/* qresult is already sorted */
result.insert(result.begin(), qresult.begin(), qresult.begin() + std::min(qresult.size(), limit));
std::sort(result.begin(), result.end(), [](const LogEntryInfo& a, const LogEntryInfo& b){
if(a.timestamp == b.timestamp)
return &a > &b;
return a.timestamp > b.timestamp;
});
if(limit > 0 && result.size() > limit)
result.erase(result.begin() + limit, result.end());
}
}
return result;
}

View File

@ -0,0 +1,843 @@
#pragma once
#include <Definitions.h>
#include <optional>
#include <cstring>
#include <utility>
#include <vector>
#include <sql/SqlQuery.h>
#include <Properties.h>
#include <PermissionManager.h>
#include <query/command3.h>
namespace ts::server {
class ConnectedClient;
class QueryClient;
}
namespace ts::server::log {
struct Invoker {
std::string name{};
std::string unique_id{};
ClientDbId database_id{0};
};
extern Invoker kInvokerSystem;
enum struct LoggerGroup {
CUSTOM,
SERVER,
CHANNEL,
PERMISSION,
CLIENT,
FILES,
QUERY,
MAX
};
constexpr static std::array<std::string_view, (int) LoggerGroup::MAX> kLoggerGroupName{
"custom",
"server",
"channel",
"permission",
"client",
"files",
"query"
};
enum struct Action {
UNKNOWN,
SERVER_CREATE,
SERVER_START,
SERVER_EDIT,
SERVER_STOP,
SERVER_DELETE,
CHANNEL_CREATE,
CHANNEL_EDIT,
CHANNEL_DELETE,
PERMISSION_ADD_VALUE,
PERMISSION_ADD_GRANT,
PERMISSION_EDIT_VALUE,
PERMISSION_EDIT_GRANT,
PERMISSION_REMOVE_VALUE,
PERMISSION_REMOVE_GRANT,
GROUP_CREATE,
GROUP_RENAME,
GROUP_PERMISSION_COPY,
GROUP_DELETE,
GROUP_ASSIGNMENT_ADD,
GROUP_ASSIGNMENT_REMOVE,
CLIENT_JOIN,
CLIENT_EDIT,
CLIENT_MOVE,
CLIENT_KICK,
CLIENT_LEAVE,
FILE_UPLOAD,
FILE_DOWNLOAD,
FILE_RENAME,
FILE_DELETE,
FILE_DIRECTORY_CREATE,
CUSTOM_LOG,
QUERY_AUTHENTICATE,
QUERY_JOIN,
QUERY_LEAVE,
MAX
};
constexpr static std::array<std::string_view, (int) Action::MAX> kActionName{
"unknown",
"server-create",
"server-start",
"server-edit",
"server-stop",
"server-delete",
"channel-create",
"channel-edit",
"channel-delete",
"permission-add-value",
"permission-add-grant",
"permission-edit-value",
"permission-edit-grant",
"permission-remove-value",
"permission-remove-grant",
"group-create",
"group-rename",
"group-permission-copy",
"group-delete",
"group-assignment-add",
"group-assignment-remove",
"client-join",
"client-edit",
"client-move",
"client-kick",
"client-leave",
"file-upload",
"file-download",
"file-rename",
"file-delete",
"file-directory-create",
"custom-log",
"query-authenticate",
"query-join",
"query-leave"
};
struct LogEntryInfo {
std::chrono::system_clock::time_point timestamp{};
Action action{Action::UNKNOWN};
standalone_command_builder_bulk info{};
};
class ActionLogger;
class AbstractActionLogger {
public:
explicit AbstractActionLogger(ActionLogger* impl) : logger_{impl} {}
[[nodiscard]] virtual bool setup(int /* database version */, std::string& /* error */) = 0;
virtual void finalize() { this->enabled_ = false; }
[[nodiscard]] inline ActionLogger* logger() const { return this->logger_; }
[[nodiscard]] inline bool enabled() const { return this->enabled_; }
inline void set_enabled(bool flag) { this->enabled_ = flag; }
[[nodiscard]] virtual std::deque<LogEntryInfo> query(
uint64_t /* server id */,
const std::chrono::system_clock::time_point& /* begin timestamp */,
const std::chrono::system_clock::time_point& /* end timestamp */,
size_t /* limit */
) = 0;
protected:
ActionLogger* logger_;
bool enabled_{false};
void do_log(ServerId /* server id */, Action /* action */, sql::command& /* command */) const;
};
struct DatabaseColumn {
std::string name{};
std::string type{};
};
struct FixedHeaderKeys {
DatabaseColumn timestamp{};
DatabaseColumn server_id{};
DatabaseColumn invoker_id{};
DatabaseColumn invoker_name{};
DatabaseColumn action{};
};
template <typename>
using T2DatabaseColumn = DatabaseColumn;
struct FixedHeaderValues {
std::chrono::system_clock::time_point timestamp{};
ServerId server_id{0};
Invoker invoker{};
Action action{};
};
template <typename... LoggingArguments>
class TypedActionLogger : public AbstractActionLogger {
public:
constexpr static auto kFixedHeaderColumnCount = 5;
static inline std::string value_binding_name(size_t index) {
return ":v" + std::to_string(index);
}
static void compile_model(sql::model& result, const std::string& command, const ActionLogger* logger);
static void register_invoker(ActionLogger* logger, const Invoker& invoker);
static std::string generate_insert(
const std::string& /* table name */,
const FixedHeaderKeys& /* headers */,
DatabaseColumn* /* payload columns */,
size_t /* payload column count */
);
static sql::command compile_query(
ActionLogger* /* logger */,
uint64_t /* server id */,
const std::chrono::system_clock::time_point& /* begin timestamp */,
const std::chrono::system_clock::time_point& /* end timestamp */,
size_t /* limit */,
const std::string& /* table name */,
const FixedHeaderKeys& /* headers */,
DatabaseColumn* /* payload columns */,
size_t /* payload column count */
);
static bool create_table(
std::string& /* error */,
ActionLogger* /* logger */,
const std::string& /* table name */,
const FixedHeaderKeys& /* headers */,
DatabaseColumn* /* payload columns */,
size_t /* payload column count */
);
static void bind_fixed_header(sql::command& /* command */, const FixedHeaderValues& /* values */);
static bool parse_fixed_header(FixedHeaderValues& /* result */, std::string* /* values */, size_t /* values length */);
explicit TypedActionLogger(ActionLogger* impl, std::string table_name, FixedHeaderKeys fh_keys, const T2DatabaseColumn<LoggingArguments>&... keys)
: AbstractActionLogger{impl}, fh_keys{std::move(fh_keys)}, table_name{std::move(table_name)} {
this->bind_key(0, keys...);
}
[[nodiscard]] std::deque<LogEntryInfo> query(
uint64_t server_id,
const std::chrono::system_clock::time_point& begin_timestamp,
const std::chrono::system_clock::time_point& end_timestamp,
size_t limit
) override {
std::deque<LogEntryInfo> result{};
auto sql_command = TypedActionLogger<>::compile_query(this->logger_, server_id, begin_timestamp, end_timestamp, limit, this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size());
FixedHeaderValues header{};
auto sql_result = sql_command.query([&](int length, std::string* values, std::string* names) {
if(length < kFixedHeaderColumnCount + sizeof...(LoggingArguments))
return;
if(!TypedActionLogger<>::parse_fixed_header(header, values, length))
return;
auto& entry = result.emplace_back();
entry.action = header.action;
entry.timestamp = header.timestamp;
auto& info = entry.info;
info.reserve(160);
info.put_unchecked("timestamp", std::chrono::floor<std::chrono::milliseconds>(header.timestamp.time_since_epoch()).count());
info.put_unchecked("invoker_database_id", header.invoker.database_id);
info.put_unchecked("invoker_nickname", header.invoker.name);
info.put_unchecked("action", kActionName[(int) header.action]);
for(size_t index{0}; index < sizeof...(LoggingArguments); index++)
info.put_unchecked(this->payload_column_names[index].name, values[index + kFixedHeaderColumnCount]);
});
return result;
};
protected:
void do_log(const FixedHeaderValues& fhvalues, const LoggingArguments&... vvalues) {
if(!this->enabled_)
return;
sql::command command = this->sql_model.command();
TypedActionLogger<>::bind_fixed_header(command, fhvalues);
TypedActionLogger::bind_value(command, 0, vvalues...);
AbstractActionLogger::do_log(fhvalues.server_id, fhvalues.action, command);
TypedActionLogger<>::register_invoker(this->logger_, fhvalues.invoker);
}
bool setup(int db_version, std::string &error) override {
TypedActionLogger<>::compile_model(
this->sql_model,
TypedActionLogger<>::generate_insert(this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size()),
this->logger_);
if(db_version == 0) {
if(!TypedActionLogger<>::create_table(error, this->logger_, this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size()))
return false;
}
return true;
}
private:
template <typename T, typename... Rest>
static void bind_value(sql::command& result, int index, const T& value, const Rest&... vvalues) {
result.value(TypedActionLogger::value_binding_name(index), value);
TypedActionLogger::bind_value(result, index + 1, vvalues...);
}
static void bind_value(sql::command&, int) {}
FixedHeaderKeys fh_keys;
sql::model sql_model{nullptr};
std::string table_name;
std::array<DatabaseColumn, sizeof...(LoggingArguments)> payload_column_names{};
template <typename T, typename... Rest>
void bind_key(int index, const T& value, const Rest&... vkeys) {
this->payload_column_names[index] = value;
this->bind_key(index + 1, vkeys...);
}
void bind_key(int) {}
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `property`, `value_old`, `value_new`
class ServerEditActionLogger : public TypedActionLogger<std::string_view, std::string, std::string> {
public:
explicit ServerEditActionLogger(ActionLogger* impl);
bool setup(int, std::string &) override;
void log_server_edit(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
const property::PropertyDescription& /* property */,
const std::string& /* old value */,
const std::string& /* new value */);
};
enum struct ServerCreateReason {
USER_ACTION,
SNAPSHOT_DEPLOY,
INITIAL_SERVER,
MAX
};
constexpr static std::array<std::string_view, (int) ServerCreateReason::MAX> kServerCreateReasonName {
"user-action",
"snapshot-deploy",
"initial-server"
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `reason`
class ServerActionLogger : public TypedActionLogger<std::string_view> {
public:
explicit ServerActionLogger(ActionLogger* impl);
bool setup(int, std::string &) override;
void log_server_create(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
ServerCreateReason /* create reason */);
void log_server_delete(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */);
void log_server_start(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */);
void log_server_stop(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */);
};
enum struct ChannelDeleteReason {
PARENT_DELETED,
USER_ACTION,
SERVER_STOP,
EMPTY,
MAX
};
constexpr static std::array<std::string_view, (int) ChannelDeleteReason::MAX> kChannelDeleteReasonName {
"parent-deleted",
"user-action",
"server-stop",
"empty"
};
enum struct ChannelType {
TEMPORARY,
SEMI_PERMANENT,
PERMANENT,
MAX
};
constexpr static std::array<std::string_view, (int) ChannelType::MAX> kChannelTypeName {
"temporary",
"semi-permanent",
"permanent"
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `property` (may be the delete reason), `value_old`, `value_new`
class ChannelActionLogger : public TypedActionLogger<ChannelId, std::string_view, std::string, std::string> {
public:
explicit ChannelActionLogger(ActionLogger* impl);
bool setup(int, std::string &) override;
void log_channel_create(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
ChannelId /* channel id */,
ChannelType /* type */);
void log_channel_move(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
ChannelId /* channel id */,
ChannelId /* old parent channel */,
ChannelId /* new parent channel */,
ChannelId /* old channel order */,
ChannelId /* new channel order */);
void log_channel_edit(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
ChannelId /* channel id */,
const property::PropertyDescription& /* property */,
const std::string& /* old value */,
const std::string& /* new value */);
void log_channel_delete(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
ChannelId /* channel id */,
ChannelDeleteReason /* reason */);
};
enum struct PermissionTarget {
SERVER_GROUP,
CHANNEL_GROUP,
CHANNEL,
CLIENT,
CLIENT_CHANNEL,
PLAYLIST,
PLAYLIST_CLIENT,
MAX
};
constexpr static std::array<std::string_view, (int) PermissionTarget::MAX> kPermissionTargetName {
"server-group",
"channel-group",
"channel",
"client",
"client-channel",
"playlist",
"playlist-client"
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target`, `id1`, `id1_name`, `id2`, `id2_name`, `permission`, `old_value`, `old_negated`, `old_skipped`, `new_value`, `new_negated`, `new_skipped`
class PermissionActionLogger : public TypedActionLogger<std::string_view, uint64_t, std::string, uint64_t, std::string, std::string, int32_t, bool, bool, int32_t, bool, bool> {
public:
explicit PermissionActionLogger(ActionLogger* impl);
bool setup(int, std::string &) override;
void log_permission_add_value(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* value */,
bool /* negated */,
bool /* skipped */);
void log_permission_add_grant(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* value */);
void log_permission_edit_value(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* old value */,
bool /* old negated */,
bool /* old skipped */,
int32_t /* new value */,
bool /* new negated */,
bool /* new skipped */);
void log_permission_edit_grant(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* old value */,
int32_t /* new value */);
void log_permission_remove_value(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* old value */,
bool /* old negate */,
bool /* old skip */);
void log_permission_remove_grant(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
server::log::PermissionTarget /* target */,
uint64_t /* id1 */, const std::string& /* id1 name */,
uint64_t /* id2 */, const std::string& /* id2 name */,
const permission::PermissionTypeEntry& /* permission */,
int32_t /* old value */);
};
enum struct GroupType {
NORMAL,
TEMPLATE,
QUERY,
MAX
};
constexpr static std::array<std::string_view, (int) GroupType::MAX> kGroupTypeName {
"normal",
"template",
"query"
};
enum struct GroupTarget {
SERVER,
CHANNEL,
MAX
};
constexpr static std::array<std::string_view, (int) GroupTarget::MAX> kGroupTargetName {
"server",
"channel"
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `type`, `target`, `group`, `group_name`, `source`, `source_name`
class GroupActionLogger : public TypedActionLogger<std::string_view, std::string_view, uint64_t, std::string, uint64_t, std::string> {
public:
explicit GroupActionLogger(ActionLogger* impl);
bool setup(int, std::string &) override;
void log_group_create(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
GroupType /* type */,
uint64_t /* group */,
const std::string& /* group name */,
uint64_t /* source */,
const std::string& /* source name */
);
void log_group_rename(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
GroupType /* type */,
uint64_t /* group */,
const std::string& /* target name */,
const std::string& /* source name */
);
void log_group_permission_copy(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
GroupType /* type */,
uint64_t /* target group */,
const std::string& /* target group name */,
uint64_t /* source */,
const std::string& /* source name */
);
void log_group_delete(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
GroupType /* type */,
uint64_t /* group */,
const std::string& /* group name */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target`, `group`, `group_name`, `client`, `client_name`
class GroupAssignmentActionLogger : public TypedActionLogger<std::string_view, uint64_t, std::string, uint64_t, std::string> {
public:
explicit GroupAssignmentActionLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_group_assignment_add(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
uint64_t /* group */,
const std::string& /* group name */,
uint64_t /* client */,
const std::string& /* client name */
);
void log_group_assignment_remove(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* editor (may be null) */,
GroupTarget /* target */,
uint64_t /* group */,
const std::string& /* group name */,
uint64_t /* client */,
const std::string& /* client name */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target_client`, `target_client_name`, `source_channel`, `source_channel_name`, `target_channel`, `target_channel_name`
class ClientChannelActionLogger : public TypedActionLogger<uint64_t, std::string, uint64_t, std::string, uint64_t, std::string> {
public:
explicit ClientChannelActionLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_client_join(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* subject */,
uint64_t /* target channel */,
const std::string& /* target channel name */
);
void log_client_move(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* issuer (may be null) */,
const std::shared_ptr<ConnectedClient>& /* subject */,
uint64_t /* target channel */,
const std::string& /* target channel name */,
uint64_t /* source channel */,
const std::string& /* source channel name */
);
void log_client_kick(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* issuer (may be null) */,
const std::shared_ptr<ConnectedClient>& /* subject */,
uint64_t /* target channel */,
const std::string& /* target channel name */,
uint64_t /* source channel */,
const std::string& /* source channel name */
);
void log_client_leave(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* subject */,
uint64_t /* source channel */,
const std::string& /* source channel name */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target_client`, `target_client_name`, `property`, `old_value`, `new_value`
class ClientEditActionLogger : public TypedActionLogger<uint64_t, std::string, std::string_view, std::string, std::string> {
public:
explicit ClientEditActionLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_client_edit(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* issuer (may be null) */,
const std::shared_ptr<ConnectedClient>& /* subject */,
const property::PropertyDescription& /* property */,
const std::string& /* old value */,
const std::string& /* new value */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `source_channel_id`, `source_path`, `target_channel_id`, `target_path`
class FilesActionLogger : public TypedActionLogger<uint64_t, std::string, uint64_t, std::string> {
public:
explicit FilesActionLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_file_upload(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
uint64_t /* channel id */,
const std::string& /* path */
);
void log_file_download(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
uint64_t /* channel id */,
const std::string& /* path */
);
void log_file_rename(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
uint64_t /* source channel id */,
const std::string& /* source name */,
uint64_t /* target channel id */,
const std::string& /* target name */
);
void log_file_delete(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
uint64_t /* channel id */,
const std::string& /* source name */
);
void log_file_directory_create(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
uint64_t /* channel id */,
const std::string& /* path */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `message`
class CustomLogger : public TypedActionLogger<std::string> {
public:
explicit CustomLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void add_log_message(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* issuer (may be null) */,
const std::string& /* message */
);
};
enum struct QueryAuthenticateResult {
SUCCESS,
UNKNOWN_USER,
INVALID_PASSWORD,
MAX
};
constexpr static std::array<std::string_view, (int) QueryAuthenticateResult::MAX> kQueryAuthenticateResultName {
"success",
"unknown-user",
"invalid-password"
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `result`
class QueryAuthenticateLogger : public TypedActionLogger<std::string, std::string, std::string_view> {
public:
explicit QueryAuthenticateLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_query_authenticate(
ServerId /* server id */,
const std::shared_ptr<QueryClient>& /* query */,
const std::string& /* username (if empty he logs out) */,
QueryAuthenticateResult /* result */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `source_server`
class QueryServerLogger : public TypedActionLogger<std::string, std::string, bool> {
public:
explicit QueryServerLogger(ActionLogger *impl);
bool setup(int, std::string &) override;
void log_query_switch(
const std::shared_ptr<QueryClient>& /* query */,
const std::string& /* authenticated username */,
ServerId /* source */,
ServerId /* target */
);
};
/*
VIRTUALSERVER_LOG_CLIENT, (Bans: TODO!, Updates: Done, Channel Tree: Done)
VIRTUALSERVER_LOG_QUERY, (Check))
VIRTUALSERVER_LOG_CHANNEL, (Check)
VIRTUALSERVER_LOG_PERMISSIONS, (Check)
VIRTUALSERVER_LOG_SERVER, (Check; Groups: Check; Assignments: Check; Administrate: Check)
VIRTUALSERVER_LOG_FILETRANSFER (Check)
*/
class ActionLogger {
public:
explicit ActionLogger();
~ActionLogger();
[[nodiscard]] bool initialize(std::string& /* error */);
void finalize();
[[nodiscard]] inline sql::SqlManager* sql_manager() const { return this->sql_handle; }
void register_invoker(const Invoker&);
[[nodiscard]] virtual std::vector<LogEntryInfo> query(
std::vector<LoggerGroup> /* groups */,
uint64_t /* server id */,
const std::chrono::system_clock::time_point& /* begin timestamp */,
const std::chrono::system_clock::time_point& /* end timestamp */,
size_t /* limit */
);
ServerActionLogger server_logger{this};
ServerEditActionLogger server_edit_logger{this};
ChannelActionLogger channel_logger{this};
PermissionActionLogger permission_logger{this};
GroupActionLogger group_logger{this};
GroupAssignmentActionLogger group_assignment_logger{this};
ClientChannelActionLogger client_channel_logger{this};
ClientEditActionLogger client_edit_logger{this};
FilesActionLogger file_logger{this};
CustomLogger custom_logger{this};
QueryServerLogger query_logger{this};
QueryAuthenticateLogger query_authenticate_logger{this};
private:
sql::SqlManager* sql_handle{nullptr};
};
}

View File

@ -0,0 +1,898 @@
//
// Created by WolverinDEV on 26/06/2020.
//
#include "ActionLogger.h"
#include "../client/ConnectedClient.h"
#include "../client/query/QueryClient.h"
using namespace ts::server;
using namespace ts::server::log;
static FixedHeaderKeys kDefaultHeaderFields{
.timestamp = {"timestamp", "BIGINT"},
.server_id = {"server_id", "INT"},
.invoker_id = {"invoker_database_id", "BIGINT"},
.invoker_name = {"invoker_name", "VARCHAR(128)"},
.action = {"action", "VARCHAR(64)"}
};
inline Invoker client_to_invoker(const std::shared_ptr<ConnectedClient>& client) {
return client ? Invoker{
.name = client->getDisplayName(),
.unique_id = client->getUid(),
.database_id = client->getClientDatabaseId()
} : kInvokerSystem;
}
/* server action logger */
bool ServerActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
ServerActionLogger::ServerActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_server",
kDefaultHeaderFields,
{"reason", "VARCHAR(64)"}
} { }
void ServerActionLogger::log_server_create(ServerId sid, const std::shared_ptr<ConnectedClient> &invoker, ServerCreateReason reason) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(invoker),
.action = Action::SERVER_CREATE,
}, kServerCreateReasonName[(int) reason]);
}
void ServerActionLogger::log_server_delete(ServerId sid, const std::shared_ptr<ConnectedClient> &invoker) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(invoker),
.action = Action::SERVER_DELETE,
}, "");
}
void ServerActionLogger::log_server_start(ServerId sid, const std::shared_ptr<ConnectedClient> &invoker) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(invoker),
.action = Action::SERVER_START,
}, "");
}
void ServerActionLogger::log_server_stop(ServerId sid, const std::shared_ptr<ConnectedClient> &invoker) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(invoker),
.action = Action::SERVER_STOP,
}, "");
}
/* -------------------------- server edit logger -------------------------- */
bool ServerEditActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
ServerEditActionLogger::ServerEditActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_server_edit",
kDefaultHeaderFields,
{"property", "VARCHAR(128)"},
{"value_old", "TEXT"},
{"value_new", "TEXT"}
} { }
void ServerEditActionLogger::log_server_edit(ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
const property::PropertyDescription &property,
const std::string &old_value,
const std::string &new_value) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::SERVER_EDIT,
}, property.name, old_value, new_value);
}
/* -------------------------- channel logger -------------------------- */
bool ChannelActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
ChannelActionLogger::ChannelActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_channel",
kDefaultHeaderFields,
{"channel_id", "BIGINT"},
{"property", "VARCHAR(128)"},
{"value_old", "TEXT"},
{"value_new", "TEXT"}
} { }
void ChannelActionLogger::log_channel_create(ServerId sid, const std::shared_ptr<ConnectedClient> &client, ChannelId cid, ChannelType type) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::CHANNEL_CREATE,
}, cid, kChannelTypeName[(int) type], "", "");
}
void ChannelActionLogger::log_channel_edit(ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
ChannelId cid,
const property::PropertyDescription &property,
const std::string &old_value,
const std::string &new_value) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::CHANNEL_EDIT,
}, cid, property.name, old_value, new_value);
}
void ChannelActionLogger::log_channel_move(ServerId sid, const std::shared_ptr<ConnectedClient> &client, ChannelId cid, ChannelId opcid, ChannelId npcid, ChannelId oco, ChannelId nco) {
auto timestamp = std::chrono::system_clock::now();
if(opcid != npcid) {
this->do_log({
.timestamp = timestamp,
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::CHANNEL_EDIT,
}, cid, "channel_parent_id", std::to_string(opcid), std::to_string(npcid));
}
if(oco != nco) {
this->do_log({
.timestamp = timestamp,
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::CHANNEL_EDIT,
}, cid, "channel_order", std::to_string(oco), std::to_string(nco));
}
}
void ChannelActionLogger::log_channel_delete(ServerId sid, const std::shared_ptr<ConnectedClient> &client, ChannelId cid, ChannelDeleteReason reason) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::CHANNEL_DELETE,
}, cid, kChannelDeleteReasonName[(int) reason], "", "");
}
/* -------------------------- permission logger -------------------------- */
bool PermissionActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
PermissionActionLogger::PermissionActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_permission",
kDefaultHeaderFields,
{"target", "VARCHAR(64)"},
{"id1", "BIGINT"},
{"id1_name", "VARCHAR(128)"},
{"id2", "BIGINT"},
{"id2_name", "VARCHAR(128)"},
{"permission", "VARCHAR(64)"},
{"old_value", "BIGINT"},
{"old_negated", "INT(1)"},
{"old_skipped", "INT(1)"},
{"new_value", "BIGINT"},
{"new_negated", "INT(1)"},
{"new_skipped", "INT(1)"}
} { }
void PermissionActionLogger::log_permission_add_value(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t new_value,
bool new_negated,
bool new_skipped) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_ADD_VALUE,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, 0, false, false, new_value, new_negated, new_skipped);
}
void PermissionActionLogger::log_permission_add_grant(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t new_value) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_ADD_GRANT,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, 0, false, false, new_value, false, false);
}
void PermissionActionLogger::log_permission_edit_value(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t old_value,
bool old_negated,
bool old_skipped,
int32_t new_value,
bool new_negated,
bool new_skipped) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_EDIT_VALUE,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, old_negated, old_skipped, new_value, new_negated, new_skipped);
}
void PermissionActionLogger::log_permission_edit_grant(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t old_value,
int32_t new_value) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_EDIT_GRANT,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, false, false, new_value, false, false);
}
void PermissionActionLogger::log_permission_remove_value(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t old_value,
bool old_negated,
bool old_skipped) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_REMOVE_VALUE,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, old_negated, old_skipped, 0, false, false);
}
void PermissionActionLogger::log_permission_remove_grant(
ServerId sid,
const std::shared_ptr<ConnectedClient>& client,
PermissionTarget target,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name,
const permission::PermissionTypeEntry& permission,
int32_t old_value) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::PERMISSION_REMOVE_GRANT,
}, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, false, false, 0, false, false);
}
/* -------------------------- group logger -------------------------- */
constexpr auto kTableLogsGroupCreateSqlite = R"(
CREATE TABLE `logs_groups` (
`id` INTEGER PRIMARY KEY NOT NULL,
`timestamp` BIGINT,
`server_id` INTEGER,
`invoker_database_id` BIGINT,
`invoker_name` VARCHAR(128),
`action` VARCHAR(64),
`type` VARCHAR(64),
`target` VARCHAR(64),
`group` BIGINT,
`group_name` VARCHAR(128),
`source` BIGINT,
`source_name` VARCHAR(128)
);
)";
bool GroupActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
GroupActionLogger::GroupActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_groups",
kDefaultHeaderFields,
{"type", "VARCHAR(64)"},
{"target", "VARCHAR(64)"},
{"group", "BIGINT"},
{"group_name", "VARCHAR(128)"},
{"source", "BIGINT"},
{"source_name", "VARCHAR(128)"}
} { }
void GroupActionLogger::log_group_create(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
GroupType type,
uint64_t gid,
const std::string &name,
uint64_t sgid,
const std::string &sname) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_CREATE,
}, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, sgid, sname);
}
void GroupActionLogger::log_group_permission_copy(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
GroupType type,
uint64_t gid,
const std::string &name,
uint64_t sgid,
const std::string &sname) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_PERMISSION_COPY,
}, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, sgid, sname);
}
void GroupActionLogger::log_group_rename(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
GroupType type,
uint64_t gid,
const std::string &name,
const std::string &sname) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_RENAME,
}, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, 0, sname);
}
void GroupActionLogger::log_group_delete(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
GroupType type,
uint64_t gid,
const std::string &name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_DELETE,
}, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, 0, "");
}
/* -------------------------- group assignment logger -------------------------- */
bool GroupAssignmentActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
GroupAssignmentActionLogger::GroupAssignmentActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_group_assignments",
kDefaultHeaderFields,
{"target", "VARCHAR(64)"},
{"group", "BIGINT"},
{"group_name", "VARCHAR(128)"},
{"client", "BIGINT"},
{"client_name", "VARCHAR(128)"}
} { }
void GroupAssignmentActionLogger::log_group_assignment_add(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
uint64_t gid,
const std::string &gname,
uint64_t tclient,
const std::string &client_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_ASSIGNMENT_ADD,
}, kGroupTargetName[(int) target], gid, gname, tclient, client_name);
}
void GroupAssignmentActionLogger::log_group_assignment_remove(
ts::ServerId sid,
const std::shared_ptr<ConnectedClient> &client,
GroupTarget target,
uint64_t gid,
const std::string &gname,
uint64_t tclient,
const std::string &client_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(client),
.action = Action::GROUP_ASSIGNMENT_REMOVE,
}, kGroupTargetName[(int) target], gid, gname, tclient, client_name);
}
/* -------------------------- group assignment logger -------------------------- */
bool ClientChannelActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
ClientChannelActionLogger::ClientChannelActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_client_channel",
kDefaultHeaderFields,
{"target_client", "BIGINT"},
{"target_client_name", "VARCHAR(128)"},
{"source_channel", "BIGINT"},
{"source_channel_name", "VARCHAR(128)"},
{"target_channel", "BIGINT"},
{"target_channel_name", "VARCHAR(128)"}
} { }
void ClientChannelActionLogger::log_client_join(
ServerId sid,
const std::shared_ptr<ConnectedClient>& subject,
uint64_t target_channel,
const std::string& target_channel_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = kInvokerSystem,
.action = Action::CLIENT_JOIN,
}, subject->getClientDatabaseId(), subject->getDisplayName(), 0, "", target_channel, target_channel_name);
}
void ClientChannelActionLogger::log_client_leave(
ServerId sid,
const std::shared_ptr<ConnectedClient>& subject,
uint64_t source_channel,
const std::string& source_channel_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = kInvokerSystem,
.action = Action::CLIENT_LEAVE,
}, subject->getClientDatabaseId(), subject->getDisplayName(), source_channel, source_channel_name, 0, "");
}
void ClientChannelActionLogger::log_client_move(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
const std::shared_ptr<ConnectedClient> &subject,
uint64_t target_channel, const std::string &target_channel_name, uint64_t source_channel, const std::string &source_channel_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::CLIENT_MOVE,
}, subject->getClientId(), subject->getDisplayName(), source_channel, source_channel_name, target_channel, target_channel_name);
}
void ClientChannelActionLogger::log_client_kick(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
const std::shared_ptr<ConnectedClient> &subject,
uint64_t target_channel, const std::string &target_channel_name, uint64_t source_channel, const std::string &source_channel_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::CLIENT_KICK,
}, subject->getClientId(), subject->getDisplayName(), source_channel, source_channel_name, target_channel, target_channel_name);
}
/* -------------------------- group assignment logger -------------------------- */
bool ClientEditActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
ClientEditActionLogger::ClientEditActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_client_edit",
kDefaultHeaderFields,
{"target_client", "BIGINT"},
{"target_client_name", "VARCHAR(128)"},
{"property", "VARCHAR(128)"},
{"old_value", "TEXT"},
{"new_value", "TEXT"}
} { }
void ClientEditActionLogger::log_client_edit(
ServerId sid,
const std::shared_ptr<ConnectedClient>& issuer,
const std::shared_ptr<ConnectedClient>& subject,
const property::PropertyDescription& property,
const std::string& old_value,
const std::string& new_value
) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::CLIENT_EDIT,
}, subject->getClientId(), subject->getDisplayName(), property.name, old_value, new_value);
}
/* -------------------------- file transfer logger -------------------------- */
bool FilesActionLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
FilesActionLogger::FilesActionLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_files",
kDefaultHeaderFields,
{"source_channel_id", "BIGINT"},
{"source_path", "VARCHAR(256)"},
{"target_channel_id", "BIGINT"},
{"target_path", "VARCHAR(256)"}
} { }
void FilesActionLogger::log_file_upload(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
uint64_t channel_id,
const std::string &path) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::FILE_UPLOAD,
}, 0, "", channel_id, path);
}
void FilesActionLogger::log_file_download(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
uint64_t channel_id,
const std::string &path) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::FILE_DOWNLOAD,
}, channel_id, path, 0, "");
}
void FilesActionLogger::log_file_rename(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
uint64_t old_channel_id,
const std::string &old_name,
uint64_t mew_channel_id,
const std::string &new_name) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::FILE_RENAME,
}, old_channel_id, old_name, mew_channel_id, new_name);
}
void FilesActionLogger::log_file_directory_create(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
uint64_t channel_id,
const std::string &path) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::FILE_DIRECTORY_CREATE,
}, 0, "", channel_id, path);
}
void FilesActionLogger::log_file_delete(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
uint64_t channel_id,
const std::string &path) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::FILE_DELETE,
}, channel_id, path, 0, "");
}
/* -------------------------- custom logger -------------------------- */
bool CustomLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
CustomLogger::CustomLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_custom",
kDefaultHeaderFields,
{"message", "TEXT"},
} { }
void CustomLogger::add_log_message(
ServerId sid,
const std::shared_ptr<ConnectedClient> &issuer,
const std::string &message) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = sid,
.invoker = client_to_invoker(issuer),
.action = Action::CUSTOM_LOG,
}, message);
}
#if 0
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `result`
class QueryAuthenticateLogger : public TypedActionLogger<std::string, std::string, bool> {
public:
void log_query_authenticate(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* query */,
const std::string& /* username (if empty he logs out) */,
bool /* success */
);
};
//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `source_server`
class QueryServerLogger : public TypedActionLogger<std::string, std::string, bool> {
public:
void log_query_switch(
ServerId /* server id */,
const std::shared_ptr<ConnectedClient>& /* query */,
const std::string& /* authenticateed username */,
ServerId /* source */
);
};
#endif
/* -------------------------- query authenticate logger -------------------------- */
bool QueryAuthenticateLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
QueryAuthenticateLogger::QueryAuthenticateLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_query_authenticate",
kDefaultHeaderFields,
{"ip", "VARCHAR(64)"},
{"username", "VARCHAR(128)"},
{"result", "INT(1)"}
} { }
void QueryAuthenticateLogger::log_query_authenticate(
ServerId server,
const std::shared_ptr<QueryClient> &query,
const std::string &username,
QueryAuthenticateResult result) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = server,
.invoker = client_to_invoker(query),
.action = Action::QUERY_AUTHENTICATE,
}, query->getLoggingPeerIp(), username, kQueryAuthenticateResultName[(int) result]);
}
/* -------------------------- query server logger -------------------------- */
bool QueryServerLogger::setup(int version, std::string &error) {
if(!TypedActionLogger::setup(version, error))
return false;
switch (version) {
case 0:
case 1:
/* up to date, nothing to do */
return true;
default:
error = "invalid database source version";
return false;
}
}
QueryServerLogger::QueryServerLogger(ActionLogger* impl) : TypedActionLogger{
impl,
"logs_query_server",
kDefaultHeaderFields,
{"ip", "VARCHAR(64)"},
{"username", "VARCHAR(128)"},
{"other_server", "INT"}
} { }
void QueryServerLogger::log_query_switch(const std::shared_ptr<QueryClient> &query, const std::string &username, ServerId source, ServerId target) {
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = source,
.invoker = client_to_invoker(query),
.action = Action::QUERY_LEAVE,
}, query->getLoggingPeerIp(), username, target);
this->do_log({
.timestamp = std::chrono::system_clock::now(),
.server_id = target,
.invoker = client_to_invoker(query),
.action = Action::QUERY_JOIN,
}, query->getLoggingPeerIp(), username, source);
}

2
shared

@ -1 +1 @@
Subproject commit f11bee75c19b437513154a9dc1c9167c72e94427
Subproject commit 809fa82b3158c78ad9604ec576fe779688de13fa