Updates for the new token system

This commit is contained in:
WolverinDEV 2021-02-25 11:13:30 +01:00
parent 22d366edc7
commit 7325caa7e8
23 changed files with 1463 additions and 323 deletions

View File

@ -220,7 +220,7 @@ target_link_libraries(PermMapHelper
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "5")
SET(CPACK_PACKAGE_VERSION_PATCH "0")
SET(CPACK_PACKAGE_VERSION_PATCH "1")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")

View File

@ -452,8 +452,6 @@ bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
flag_sql |= !res;
serverInstance->databaseHelper()->deleteGroupArtifacts(this->getServerId(), group->groupId());
if(auto server{this->server.lock()}; server)
server->getTokenManager()->handleGroupDelete(group->groupId());
if(flag_sql)
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());

View File

@ -515,11 +515,10 @@ void InstanceHandler::tickInstance() {
if(memcleanTimestamp + minutes(10) < now) {
memcleanTimestamp = now;
{
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0)
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) {
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
}
}
}
@ -532,7 +531,7 @@ void InstanceHandler::tickInstance() {
auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES";
auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; });
if(!result) {
logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Dummy sql connection test failed! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Stopping instance!");
ts::server::shutdownInstance("invalid sql connection!");
}

View File

@ -174,31 +174,74 @@ void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> cl
}
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
shared_lock server_channel_lock(this->channel_tree_lock);
std::shared_ptr<BasicChannel> channel = nullptr;
if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<string>().empty()) {
auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>();
if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos)
channel = this->channelTree->findChannel(static_cast<ChannelId>(stoll(str.substr(1))));
else
channel = this->channelTree->findChannelByPath(str);
if (channel) {
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
} else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
}
} else
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
CLIENT_STR_LOG_PREFIX_(client),
client->getDisplayName(), client->getUid(),
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
}
if(!channel) channel = this->channelTree->getDefaultChannel();
if(!channel) return false;
std::shared_lock server_channel_lock{this->channel_tree_lock};
std::shared_ptr<BasicChannel> channel{};
auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value();
if(!requested_channel_path.empty()) {
if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) {
ChannelId channel_id{0};
try {
channel_id = std::stoull(requested_channel_path.substr(1));
} catch (std::exception&) {
logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id.");
}
if(channel_id > 0) {
channel = this->channelTree->findChannel(channel_id);
}
} else {
channel = this->channelTree->findChannelByPath(requested_channel_path);
}
}
if(channel) {
/* Client proposes a target channel */
auto& channel_whitelist = client->join_whitelisted_channel;
auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); });
auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value();
if(whitelist_entry != channel_whitelist.end()) {
debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId());
if(whitelist_entry->second != "ignore") {
if (!channel->passwordMatch(client_channel_password, true)) {
if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
channel = nullptr;
goto skip_permissions;
}
}
}
goto skip_permissions;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId());
channel = nullptr;
goto skip_permissions;
}
if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true)) {
if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId());
channel = nullptr;
goto skip_permissions;
}
}
skip_permissions:;
}
if(!channel) {
/* Client did not propose a channel or the proposed channel got rejected */
channel = this->channelTree->getDefaultChannel();
if(!channel) {
logCritical(this->getServerId(), "Channel tree is missing the default channel.");
return false;
}
}
debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId());
if(join) {
server_channel_lock.unlock();
unique_lock server_channel_w_lock(this->channel_tree_lock);
@ -321,10 +364,13 @@ 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, bool temp_delete) {
if(!tree_lock.owns_lock())
if(!tree_lock.owns_lock()) {
tree_lock.lock();
if(channel->deleted)
}
if(channel->deleted) {
return;
}
deque<std::shared_ptr<ConnectedClient>> clients;
{
@ -365,6 +411,8 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
unique_lock client_channel_lock(client->channel_lock);
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
});
this->tokenManager->handle_channel_deleted(channel->channelId());
}
void VirtualServer::client_move(

View File

@ -76,6 +76,9 @@ void VirtualServer::executeServerTick() {
case ClientType::CLIENT_MUSIC:
queryOnline++;
break;
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
@ -84,8 +87,10 @@ void VirtualServer::executeServerTick() {
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - this->startTimestamp).count();
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = clientOnline + queryOnline;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = queryOnline;
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
if(clientOnline + queryOnline == 0) {
//We don't need to tick, when server is empty!
return;
}
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
END_TIMINGS(timing_update_states);
@ -149,9 +154,10 @@ void VirtualServer::executeServerTick() {
}
}
if(cl->clientPermissions->require_db_updates()) {
auto client_permissions = cl->clientPermissions;
if(client_permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), client_permissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast<milliseconds>(end - begin).count());
}

View File

@ -220,8 +220,8 @@ bool VirtualServer::initialize(bool test_properties) {
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>())
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this);
this->tokenManager->loadTokens();
this->tokenManager = new token::TokenManager(this->sql, this->getServerId());
this->tokenManager->initialize_cache();
this->complains = new ComplainManager(this);
if(!this->complains->loadComplains()) logError(this->serverId, "Could not load complains");
@ -1137,7 +1137,7 @@ VirtualServer::NetworkReport VirtualServer::generate_network_report() {
return result;
}
bool VirtualServer::resetPermissions(std::string& token) {
bool VirtualServer::resetPermissions(std::string& new_permission_token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
@ -1200,23 +1200,26 @@ bool VirtualServer::resetPermissions(std::string& token) {
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_admin->name()).front()->groupId();
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId();
auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
if(!created) {
logCritical(this->serverId, "Failed to generate default serveradmin token!");
auto server_admin_group_id = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto token = this->tokenManager->create_token(0, "Default server admin token", 1, std::chrono::system_clock::time_point{});
if(!token) {
logCritical(this->serverId, "Failed to register the default server admin token.");
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
} else {
token = created->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token;
std::vector<token::TokenAction> actions{};
actions.push_back(token::TokenAction{
.id = 0,
.type = token::ActionType::AddServerGroup,
.id1 = server_admin_group_id,
.id2 = 0
});
this->tokenManager->add_token_actions(token->id, actions);
new_permission_token = token->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token->token;
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = true;
}
if(this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY].as<bool>()) {
auto requested_token = this->tokenManager->findToken(this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY]);
if(!requested_token) {
logError(this->serverId, "Failed to resolve default token! Don't ask for privilege key anymore.");
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = "";
}
}
this->ensureValidDefaultGroups();
for(const auto& client : this->getClients()) {

View File

@ -191,8 +191,8 @@ namespace ts {
inline GroupManager* getGroupManager() { return this->groups; }
inline rtc::Server& rtc_server() { return *this->rtc_server_; }
[[nodiscard]] inline auto getTokenManager() -> token::TokenManager* {
return this->tokenManager;
[[nodiscard]] inline auto& getTokenManager() {
return *this->tokenManager;
}
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);

View File

@ -17,7 +17,6 @@ using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
extern ts::server::InstanceHandler* serverInstance;
@ -947,6 +946,11 @@ std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string&
}
bool ConnectedClient::update_client_needed_permissions() {
if(this->getType() == ClientType::CLIENT_QUERY) {
/* Query clients are not interested in their permissions */
return true;
}
/* The server and/or the channel might change while we're executing this method */
auto currentChannel = this->currentChannel;
auto values = this->calculate_permissions(permission::neededPermissions, currentChannel ? currentChannel->channelId() : 0);
@ -1001,39 +1005,164 @@ do { \
} while(0)
permission::PermissionType ConnectedClient::calculate_and_get_join_state(const std::shared_ptr<BasicChannel>& channel) {
shared_ptr<ViewEntry> ventry;
std::shared_ptr<ViewEntry> ventry;
{
shared_lock view_lock(this->channel_lock);
ventry = this->channel_view()->find_channel(channel);
if(!ventry)
if(!ventry) {
return permission::i_channel_view_power;
}
}
if(ventry->join_state_id == this->join_state_id)
if(ventry->join_state_id == this->join_state_id) {
return ventry->join_permission_error;
}
auto channel_id = channel->channelId();
auto permission_cache = make_shared<CalculateCache>();
switch(channel->channelType()) {
case ChannelType::permanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) {
RESULT(permission::b_channel_join_permanent);
}
break;
case ChannelType::semipermanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) {
RESULT(permission::b_channel_join_semi_permanent);
}
break;
case ChannelType::temporary:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) {
RESULT(permission::b_channel_join_temporary);
}
break;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, this->calculate_permission(permission::i_channel_join_power, channel_id), false)) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) {
RESULT(permission::i_channel_join_power);
}
}
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id)))
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) {
RESULT(permission::b_client_is_sticky);
}
}
RESULT(permission::ok);
}
void ConnectedClient::useToken(token::TokenId token_id) {
auto server_ref = this->server;
if(!server_ref) {
return;
}
std::deque<token::TokenAction> actions{};
if(!server_ref->getTokenManager().query_token_actions(token_id, actions)) {
return;
}
if(actions.empty()) {
return;
}
bool tree_registered = !!this->currentChannel;
bool server_groups_changed{false}, channel_group_changed{false};
std::deque<std::shared_ptr<Group>> added_server_groups{};
std::deque<std::shared_ptr<Group>> removed_server_groups{};
for(const auto& action : actions) {
switch(action.type) {
case token::ActionType::AddServerGroup:
case token::ActionType::RemoveServerGroup: {
auto group = server->getGroupManager()->findGroup(action.id1);
if(!group || group->target() != GroupTarget::GROUPTARGET_SERVER) {
debugMessage(this->getServerId(), "{} Skipping token action add/remove server group for group {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1);
break;
}
if(action.type == token::ActionType::AddServerGroup) {
if(this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) {
debugMessage(this->getServerId(), "{} Skipping token action add server group for group {} because client is already member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
} else {
debugMessage(this->getServerId(), "{} Executing token action add server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
this->server->groups->addServerGroup(this->getClientDatabaseId(), group);
added_server_groups.push_back(group);
server_groups_changed = true;
}
} else {
if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) {
debugMessage(this->getServerId(), "{} Skipping token action remove server group for group {} because client is not a member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
} else {
debugMessage(this->getServerId(), "{} Executing token action remove server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
this->server->groups->removeServerGroup(this->getClientDatabaseId(), group);
removed_server_groups.push_back(group);
server_groups_changed = true;
}
}
break;
}
case token::ActionType::SetChannelGroup: {
auto group = server->getGroupManager()->findGroup(action.id1);
if(!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
auto channel = this->server->channelTree->findChannel(action.id2);
if (!channel) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the channel does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
channel_group_changed = true;
this->server->groups->setChannelGroup(this->getClientDatabaseId(), group, channel);
break;
}
case token::ActionType::AllowChannelJoin: {
auto speaking_client = dynamic_cast<SpeakingClient*>(this);
if(speaking_client) {
speaking_client->join_whitelisted_channel.emplace_back(action.id2, action.text);
}
break;
}
case token::ActionType::ActionSqlFailed:
case token::ActionType::ActionDeleted:
case token::ActionType::ActionIgnore:
default:
break;
}
}
if(this->state > ConnectionState::INIT_HIGH) {
this->task_update_channel_client_properties.enqueue();
this->task_update_needed_permissions.enqueue();
}
if(tree_registered && (server_groups_changed || channel_group_changed)) {
auto updated_properties = this->getServer()->getGroupManager()->update_server_group_property(this->ref(), true, this->currentChannel);
if(!updated_properties.empty()) {
this->getServer()->notifyClientPropertyUpdates(this->ref(), updated_properties);
}
}
if(tree_registered && server_groups_changed) {
for(const auto &viewer : this->server->getClients()) {
if(viewer->isClientVisible(this->ref(), true)) {
for(const auto& group : added_server_groups) {
viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), group);
}
for(const auto& group : removed_server_groups) {
viewer->notifyServerGroupClientRemove(this->server->serverRoot, this->ref(), group);
}
}
}
}
}

View File

@ -10,7 +10,7 @@
#include "DataClient.h"
#include "query/command3.h"
#define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]")
#define CLIENT_STR_LOG_PREFIX_(this) (this->getLoggingPrefix())
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
#define CMD_REQ_SERVER \
@ -83,10 +83,14 @@ namespace ts {
ConnectionState connectionState(){ return this->state; }
std::string getLoggingPeerIp() { return config::server::disable_ip_saving || (this->server && this->server->disable_ip_saving()) ? "X.X.X.X" : this->getPeerIp(); }
std::string getPeerIp(){ return this->isAddressV4() ? net::to_string(this->getAddressV4()->sin_addr) : this->isAddressV6() ? net::to_string(this->getAddressV6()->sin6_addr) : "localhost"; }
uint16_t getPeerPort(){ return ntohs(this->isAddressV4() ? this->getAddressV4()->sin_port : this->isAddressV6() ? this->getAddressV6()->sin6_port : (uint16_t) 0); }
std::string getPeerIp(){ return net::to_string(this->remote_address, false); }
uint16_t getPeerPort(){ return net::port(this->remote_address); }
std::string getHardwareId(){ return properties()[property::CLIENT_HARDWARE_ID]; }
[[nodiscard]] inline std::string getLoggingPrefix() {
return std::string{"["} + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]";
}
//General connection stuff
bool isAddressV4() { return this->remote_address.ss_family == AF_INET; }
const sockaddr_in* getAddressV4(){ return (sockaddr_in*) &this->remote_address; }
@ -320,6 +324,8 @@ namespace ts {
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->subscribed_playlist_.lock() == playlist;
}
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
protected:
std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address;
@ -328,7 +334,7 @@ namespace ts {
std::mutex state_lock;
ConnectionState state{ConnectionState::UNKNWON};
bool allowedToTalk = false;
bool allowedToTalk{false};
std::shared_mutex finalDisconnectLock; /* locked before state lock! */
@ -384,9 +390,10 @@ namespace ts {
virtual void initialize_weak_reference(const std::shared_ptr<ConnectedClient>& /* self reference */);
bool subscribeToAll = false;
uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
bool subscribeToAll{false};
uint16_t join_state_id{1}; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
/* (ChannelId, ChannelPasswordHash!) (If empty no password/permissions, if "ignore" ignore permissions granted) */
std::vector<std::pair<ChannelId, std::string>> join_whitelisted_channel{}; /* Access only when the command mutex is acquired */
std::weak_ptr<MusicClient> selectedBot;
std::weak_ptr<MusicClient> subscribed_bot;
@ -496,7 +503,9 @@ namespace ts {
command_result handleCommandBanTriggerList(Command&);
command_result handleCommandTokenList(Command&);
command_result handleCommandTokenActionList(Command&);
command_result handleCommandTokenAdd(Command&);
command_result handleCommandTokenEdit(Command&);
command_result handleCommandTokenUse(Command&);
command_result handleCommandTokenDelete(Command&);
@ -646,6 +655,16 @@ namespace ts {
bool handleTextMessage(ChatMessageMode, std::string, const std::shared_ptr<ConnectedClient>& /* sender target */);
/**
* Call this method only when command handling is locked (aka the client can't do anything).
* All other locks shall be free.
*
* Note: This will not increase the token use count.
* The callee will have to do so.
*
*/
void useToken(token::TokenId);
typedef std::function<void(const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */)> handle_text_command_fn_t;
bool handle_text_command(
ChatMessageMode,

View File

@ -14,7 +14,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
# define INVOKER(command, invoker) \
do { \

View File

@ -401,6 +401,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
void SpeakingClient::processJoin() {
TIMING_START(timings);
auto ref_server = this->server;
assert(ref_server);
this->resetIdleTime();
threads::MutexLock lock(this->command_lock); //Don't process any commands!
@ -445,10 +446,10 @@ void SpeakingClient::processJoin() {
if(geoloc::provider) {
auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false);
if(loc) {
debugMessage(this->getServerId(), "Client " + this->getDisplayName() + "|" + this->getLoggingPeerIp() + " comes from " + loc->name + "|" + loc->identifier);
debugMessage(this->getServerId(), "{} Resolved ip address to {}/{}", this->getLoggingPrefix(), loc->name, loc->identifier);
this->properties()[property::CLIENT_COUNTRY] = loc->identifier;
} else {
logError(ref_server ? ref_server->getServerId() : 0, "Could not resolve country for ip " + this->getLoggingPeerIp() + "|" + this->getDisplayName());
debugMessage(this->getServerId(), "{} Could not resolve IP info for {}", this->getLoggingPrefix(), this->getLoggingPeerIp());
}
}
//this->updateChannelClientProperties(false); /* will be already updated via assignChannel */
@ -468,6 +469,25 @@ void SpeakingClient::processJoin() {
debugMessage(this->getServerId(), "Client id: " + to_string(this->getClientId()));
TIMING_STEP(timings, "notify sini");
if(auto token{this->properties()[property::CLIENT_DEFAULT_TOKEN].value()}; !token.empty()){
auto token_info = ref_server->getTokenManager().load_token(token, true);
if(token_info) {
if(token_info->is_expired()) {
debugMessage(this->getServerId(), "{} Client tried to use an expired token {}", this->getLoginName(), token);
} else if(token_info->use_limit_reached()) {
debugMessage(this->getServerId(), "{} Client tried to use an token which reached the use limit {}", this->getLoginName(), token);
} else {
debugMessage(this->getServerId(), "{} Client used token {}", this->getLoginName(), token);
ref_server->getTokenManager().log_token_use(token_info->id);
this->useToken(token_info->id);
}
} else {
debugMessage(this->getServerId(), "{} Client tried to use an unknown token {}", token);
}
}
TIMING_STEP(timings, "token use ");
if(!ref_server->assignDefaultChannel(this->ref(), false)) {
auto result = command_result{error::vs_critical, "Could not assign default channel!"};
this->notifyError(result);
@ -533,6 +553,7 @@ void SpeakingClient::processJoin() {
}
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
this->join_whitelisted_channel.clear();
serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name());
}

View File

@ -32,7 +32,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
//{findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as<string>() : cmd[index]["permsid"].as<string>())}; \
//TODO: Log missing permissions?
@ -419,6 +418,10 @@ command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) {
}
}
if(this->server) {
this->server->tokenManager->handle_channel_group_deleted(channel_group->groupId());
}
return command_result{error::ok};
}

View File

@ -33,7 +33,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
#define QUERY_PASSWORD_LENGTH 12
@ -170,15 +169,35 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
}
auto permission_cache = make_shared<CalculateCache>();
if(!cmd[0].has("cpw"))
cmd["cpw"] = "";
if (!channel->passwordMatch(cmd["cpw"], true))
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId())))
return command_result{error::channel_invalid_password};
auto& channel_whitelist = this->join_whitelisted_channel;
auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); });
if(whitelist_entry != channel_whitelist.end()) {
debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used earlier explicitly allowed it.", this->getLoggingPrefix(), channel->channelId());
if(whitelist_entry->second != "ignore") {
if (!channel->passwordMatch(cmd["cpw"], true)) {
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
return command_result{error::channel_invalid_password};
}
}
}
} else {
if(!cmd[0].has("cpw")) {
cmd["cpw"] = "";
}
auto permission_error = this->calculate_and_get_join_state(channel);
if(permission_error != permission::unknown) return command_result{permission_error};
if (!channel->passwordMatch(cmd["cpw"], true)) {
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
return command_result{error::channel_invalid_password};
}
}
auto permission_error = this->calculate_and_get_join_state(channel);
if(permission_error != permission::unknown) {
return command_result{permission_error};
}
}
channel_whitelist.clear();
command_result_bulk result{};
result.reserve(cmd.bulkCount());
@ -218,29 +237,36 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
result.emplace_result(error::ok);
}
/* FIXME: Some kind of invite key frags to prevent limit checking! */
if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as<bool>() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_maxclients, channel->channelId()))) {
if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as<bool>()) {
auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as<int32_t>();
if (maxClients >= 0 && maxClients < this->server->getClientsByChannel(channel).size() + clients.size())
if (maxClients >= 0 && maxClients < this->server->getClientsByChannel(channel).size() + clients.size()) {
return command_result{error::channel_maxclients_reached};
}
}
if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
shared_ptr<BasicChannel> family_root;
if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) {
family_root = channel;
while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>())
while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) {
family_root = family_root->parent();
}
}
if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED
auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int32_t>();
auto client_count = 0;
for(const auto& entry : this->server->getClientsByChannelRoot(channel, false))
if(entry.get() != this) client_count++; //Dont count the client itself
for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) {
if(entry.get() != this) {
client_count++; //Dont count the client itself
}
}
if (maxClients >= 0 && maxClients < client_count + clients.size())
if (maxClients >= 0 && maxClients < client_count + clients.size()) {
return command_result{error::channel_maxfamily_reached};
}
}
}
}
@ -253,7 +279,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
for(auto& client : clients) {
auto oldChannel = client->getChannel();
if(!oldChannel) continue;
if(!oldChannel) {
continue;
}
this->server->client_move(
client.client,
@ -267,8 +295,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
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())
if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr<BasicChannel>& channel) { return &*channel == &*oldChannel; }) == channels.end()) {
channels.push_back(oldChannel);
}
}
for(const auto& oldChannel : channels) {

View File

@ -34,7 +34,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
constexpr static auto kFileAPITimeout = std::chrono::milliseconds{500};
constexpr static auto kMaxClientTransfers = 10;

View File

@ -24,13 +24,13 @@
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include <misc/digest.h>
#include "helpers.h"
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/rnd.h>
#include <misc/strobf.h>
#include <bbcode/bbcodes.h>
namespace fs = std::experimental::filesystem;
@ -38,7 +38,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::token;
#define QUERY_PASSWORD_LENGTH 12
@ -164,8 +164,10 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
else if (command == "bandelall") return this->handleCommandBanDelAll(cmd);
else if (command == "bantriggerlist") return this->handleCommandBanTriggerList(cmd);
//Tokens
else if (command == "tokenactionlist") return this->handleCommandTokenActionList(cmd);
else if (command == "tokenlist" || command == "privilegekeylist") return this->handleCommandTokenList(cmd);
else if (command == "tokenadd" || command == "privilegekeyadd") return this->handleCommandTokenAdd(cmd);
else if (command == "tokenedit") return this->handleCommandTokenEdit(cmd);
else if (command == "tokenuse" || command == "privilegekeyuse") return this->handleCommandTokenUse(cmd);
else if (command == "tokendelete" || command == "privilegekeydelete") return this->handleCommandTokenDelete(cmd);
@ -1017,102 +1019,580 @@ command_result ConnectedClient::handleCommandTokenList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list, 1);
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenlist" : "");
int index = 0;
for (auto &token : this->server->tokenManager->avariableTokes()) {
notify[index]["token"] = token->token;
notify[index]["token_type"] = token->type;
notify[index]["token_id1"] = token->groupId;
notify[index]["token_id2"] = token->channelId;
notify[index]["token_created"] = chrono::duration_cast<chrono::seconds>(token->created.time_since_epoch()).count();
notify[index]["token_description"] = token->description;
index++;
auto& token_manager = this->server->getTokenManager();
size_t offset = cmd[0].has("offset") ? cmd["offset"].as<size_t>() : 0;
size_t limit = cmd[0].has("limit") ? cmd["limit"].as<int>() : 0;
if(limit > 2000 || limit < 1) {
limit = 1000;
}
if (index == 0) return command_result{error::database_empty_result};
size_t total_tokens{0};
std::deque<std::shared_ptr<Token>> tokens{};
auto own_tokens_only = cmd.hasParm("own-only") || !permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_token_list_all, 0));
if(own_tokens_only) {
tokens = token_manager.list_tokens(total_tokens, { offset }, { limit }, { this->getClientDatabaseId() });
} else {
tokens = token_manager.list_tokens(total_tokens, { offset }, { limit }, std::nullopt);
}
if(tokens.empty()) {
return ts::command_result{error::database_empty_result};
}
auto new_command = cmd.hasParm("new");
auto own_database_id = this->getClientDatabaseId();
ts::command_builder notify{this->notify_response_command("notifytokenlist")};
size_t notify_index{0};
for(size_t index{0}; index < tokens.size(); index++) {
auto& token = tokens[index];
if(own_tokens_only && token->issuer_database_id != own_database_id) {
continue;
}
auto bulk = notify.bulk(notify_index++);
bulk.put_unchecked("token", token->token);
bulk.put_unchecked("token_id", token->id);
bulk.put_unchecked("token_created", std::chrono::duration_cast<std::chrono::seconds>(token->timestamp_created.time_since_epoch()).count());
bulk.put_unchecked("token_expired", std::chrono::duration_cast<std::chrono::seconds>(token->timestamp_expired.time_since_epoch()).count());
bulk.put_unchecked("token_use_count", token->use_count);
bulk.put_unchecked("token_max_uses", token->max_uses);
bulk.put_unchecked("token_issuer_database_id", token->issuer_database_id);
bulk.put_unchecked("token_description", token->description);
if(!new_command) {
bulk.put_unchecked("token_type", 0);
bulk.put_unchecked("token_id1", 0);
bulk.put_unchecked("token_id2", 0);
}
}
if(notify_index == 0) {
return ts::command_result{error::database_empty_result};
}
notify.put_unchecked(0, "token_count", total_tokens);
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandTokenActionList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto& token_manager = this->server->getTokenManager();
std::shared_ptr<token::Token> token_info{};
if(cmd[0].has("token_id")) {
token_info = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as<token::TokenId>(), true);
} else if(cmd[0].has("token")) {
token_info = this->server->getTokenManager().load_token(cmd["token"], true);
}
if(!token_info) {
return ts::command_result{error::token_invalid_id};
}
if(token_info->issuer_database_id != this->getClientDatabaseId()) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list_all, 1);
}
std::deque<TokenAction> actions{};
if(!token_manager.query_token_actions(token_info->id, actions)) {
return ts::command_result{error::vs_critical};
}
ts::command_builder notify{this->notify_response_command("notifytokenactions")};
for(size_t index{0}; index < actions.size(); index++) {
auto bulk = notify.bulk(index);
auto& action = actions[index];
bulk.put_unchecked("action_type", (uint8_t) action.type);
bulk.put_unchecked("action_id", action.id);
bulk.put_unchecked("action_id1", action.id1);
bulk.put_unchecked("action_id2", action.id2);
bulk.put_unchecked("action_text", action.text);
}
this->sendCommand(notify);
return command_result{error::ok};
}
/**
*
* @param result Should already contain cmd.bulkCount() results
* @param cmd
* @param token_description
* @param max_uses
* @param timestamp_expired
* @param actions
* @return
*/
ts::command_result parseTokenParameters(
ts::command_result_bulk& result,
Command &cmd,
std::optional<std::string>& token_description,
std::optional<size_t>& max_uses,
std::optional<std::chrono::system_clock::time_point>& timestamp_expired,
std::vector<token::TokenAction>& actions
) {
actions.reserve(cmd.bulkCount());
/* legacy (should not be provided on token edit) */
if(cmd[0].has("tokentype")) {
auto ttype = cmd["tokentype"].as<uint32_t>();
auto gId = cmd["tokenid1"].as<GroupId>();
auto cId = cmd["tokenid2"].as<ChannelId>();
if(ttype == 0) {
auto& action = actions.emplace_back();
action.type = token::ActionType::AddServerGroup;
action.id1 = gId;
} else if(ttype == 1) {
auto& action = actions.emplace_back();
action.type = token::ActionType::SetChannelGroup;
action.id1 = gId;
action.id2 = cId;
} else {
result.set_result(0, ts::command_result{error::parameter_invalid, "tokentype"});
}
} else {
for(size_t index{0}; index < cmd.bulkCount(); index++) {
auto& action = actions.emplace_back();
action.id = cmd[index].has("action_id") ? cmd[index]["action_id"] : 0;
if(!cmd[index].has("action_type")) {
if(action.id > 0) {
/* we want to delete the action */
action.type = token::ActionType::ActionDeleted;
continue;
}
/* Missing the parameter action type */
action.type = token::ActionType::ActionIgnore;
if(index == 0) {
/* no actions provided */
break;
} else {
result.set_result(index, ts::command_result{error::parameter_missing, "action_type"});
continue;
}
}
action.type = cmd[index]["action_type"];
switch(action.type) {
case token::ActionType::AddServerGroup:
case token::ActionType::RemoveServerGroup:
case token::ActionType::SetChannelGroup:
case token::ActionType::AllowChannelJoin:
break;
case token::ActionType::ActionSqlFailed:
case token::ActionType::ActionDeleted:
case token::ActionType::ActionIgnore:
default:
result.set_result(index, ts::command_result{error::parameter_invalid, "action_type"});
continue;
}
if(cmd[index].has("action_id1")) {
action.id1 = cmd[index]["action_id1"];
}
if(cmd[index].has("action_id2")) {
action.id2 = cmd[index]["action_id2"];
}
if(cmd[index].has("action_text")) {
action.text = cmd[index]["action_text"].string();
}
}
}
if(cmd[0].has("token_description")) {
token_description = { cmd["token_description"].string() };
} else if(cmd[0].has("tokendescription")) {
token_description = { cmd["tokendescription"].string() };
}
if(token_description.has_value() && token_description->length() > 255) {
return ts::command_result{error::parameter_invalid, "token_description"};
}
if(cmd[0].has("token_max_uses")) {
max_uses = { cmd["token_max_uses"].as<size_t>() };
}
if(cmd[0].has("token_expired")) {
auto seconds = cmd["token_expired"].as<uint64_t>();
timestamp_expired = { std::chrono::system_clock::time_point{} + std::chrono::seconds{seconds} };
}
return ts::command_result{error::ok};
}
/**
* Check if the client has permissions to do such actions.
* If not the toke will be set to ignored and a proper error will be emplaced
*/
void check_token_action_permissions(const std::shared_ptr<ConnectedClient>& client, ts::command_result_bulk& result, std::vector<token::TokenAction>& actions) {
for(size_t index{0}; index < actions.size(); index++) {
auto& action = actions[index];
switch(action.type) {
case ActionType::AddServerGroup: {
auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1);
if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::group_invalid_id});
break;
}
if(!target_group->permission_granted(permission::i_server_group_needed_member_add_power, client->calculate_permission(permission::i_server_group_member_add_power, 0), true)) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{permission::i_server_group_member_add_power});
break;
}
break;
}
case ActionType::RemoveServerGroup: {
auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1);
if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::group_invalid_id});
break;
}
if(!target_group->permission_granted(permission::i_server_group_needed_member_remove_power, client->calculate_permission(permission::i_server_group_member_remove_power, 0), true)) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{permission::i_server_group_member_remove_power});
break;
}
break;
}
case ActionType::SetChannelGroup: {
auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1);
if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_CHANNEL) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::group_invalid_id});
break;
}
auto granted_permission = client->calculate_permission(permission::i_channel_group_member_add_power, action.id2);
if(!target_group->permission_granted(permission::i_channel_group_needed_member_add_power, granted_permission, true)) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{permission::i_channel_group_member_add_power});
break;
}
{
std::shared_lock tree_lock{client->getServer()->get_channel_tree_lock()};
auto target_channel = client->getServer()->getChannelTree()->findChannel(action.id2);
tree_lock.unlock();
if(!target_channel) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::channel_invalid_id});
break;
}
}
break;
}
case ActionType::AllowChannelJoin: {
std::shared_lock tree_lock{client->getServer()->get_channel_tree_lock()};
auto target_channel = client->getServer()->getChannelTree()->findChannel(action.id2);
if(!target_channel) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::channel_invalid_id});
break;
}
auto join_permission = client->calculate_and_get_join_state(target_channel);
if(join_permission != permission::ok) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{join_permission});
break;
}
if(client->getType() == ClientType::CLIENT_QUERY && !action.text.empty()) {
action.text = digest::sha1(action.text);
}
if(permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, target_channel->channelId()))) {
action.text = "ignore";
} else if(!target_channel->passwordMatch(action.text, true)) {
action.type = ActionType::ActionIgnore;
result.set_result(index, ts::command_result{error::channel_invalid_password});
break;
}
break;
}
case ActionType::ActionSqlFailed:
case ActionType::ActionIgnore:
case ActionType::ActionDeleted:
default:
continue;
}
}
}
command_result ConnectedClient::handleCommandTokenAdd(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_add, 1);
TokenType ttype = static_cast<TokenType>(cmd["tokentype"].as<uint32_t>());
auto gId = cmd["tokenid1"].as<GroupId>();
auto cId = cmd["tokenid2"].as<ChannelId>();
string description = cmd["tokendescription"].string();
{
auto group = this->server->groups->findGroup(gId);
if(!group) return command_result{error::group_invalid_id};
if(group->type() == GroupType::GROUP_TYPE_TEMPLATE) return command_result{error::parameter_invalid, "invalid server group type"};
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_member_add_power, permission::i_server_group_member_add_power, true);
else
ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_member_add_power, permission::i_channel_group_member_add_power, true);
auto client_tokens = this->server->tokenManager->client_token_count(this->getClientDatabaseId());
if(!permission::v2::permission_granted(client_tokens + 1, this->calculate_permission(permission::i_virtualserver_token_limit, 0), true)) {
return ts::command_result{permission::i_virtualserver_token_limit};
}
if (ttype == TokenType::TOKEN_CHANNEL) {
if (!this->server->channelTree->findChannel(cId)) return command_result{error::channel_invalid_id, "Cant resolve channel"};
} else if (ttype == TokenType::TOKEN_SERVER);
else return command_result{error::parameter_invalid, "invalid token target type"};
std::vector<token::TokenAction> actions{};
actions.reserve(cmd.bulkCount());
auto result = this->server->tokenManager->createToken(ttype, gId, description, cId, cmd["token"]);
if (!result) return command_result{error::vs_critical};
std::optional<std::string> token_description{};
std::optional<size_t> max_uses{};
std::optional<std::chrono::system_clock::time_point> timestamp_expired{};
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenadd" : "");
notify["token"] = result->token;
ts::command_result_bulk result{};
result.emplace_result_n(cmd.bulkCount(), error::ok);
auto parse_result = parseTokenParameters(
result,
cmd,
token_description,
max_uses,
timestamp_expired,
actions
);
if(parse_result.has_error()) {
return parse_result;
}
parse_result.release_data();
check_token_action_permissions(this->ref(), result, actions);
auto token = this->server->getTokenManager().create_token(
this->getClientDatabaseId(),
token_description.value_or(""),
max_uses.value_or(0),
timestamp_expired.value_or(std::chrono::system_clock::time_point{})
);
if(!token) {
return ts::command_result{error::vs_critical};
}
ts::command_builder notify{this->notify_response_command("notifytokenadd")};
if(!actions.empty()) {
this->server->getTokenManager().add_token_actions(token->id, actions);
for(size_t index{0}; index < actions.size(); index++) {
auto& action = actions[index];
if(action.type == token::ActionType::ActionIgnore || action.type == token::ActionType::ActionDeleted) {
continue;
} else if(action.type == token::ActionType::ActionSqlFailed) {
result.set_result(index, ts::command_result{error::database_constraint});
} else {
notify.put_unchecked(index, "action_id", action.id);
}
}
}
notify.put_unchecked(0, "token", token->token);
notify.put_unchecked(0, "token_id", token->id);
this->sendCommand(notify);
return command_result{error::ok};
return command_result{std::move(result)};
}
command_result ConnectedClient::handleCommandTokenEdit(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(15);
std::shared_ptr<token::Token> token{};
if(cmd[0].has("token_id")) {
token = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as<token::TokenId>(), true);
} else if(cmd[0].has("token")) {
token = this->server->getTokenManager().load_token(cmd["token"], true);
}
if(!token) {
return ts::command_result{error::token_invalid_id};
}
if(token->issuer_database_id != this->getClientDatabaseId()) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_edit_all, 1);
}
std::vector<token::TokenAction> actions{};
actions.reserve(cmd.bulkCount());
std::optional<std::string> token_description{};
std::optional<size_t> max_uses{};
std::optional<std::chrono::system_clock::time_point> timestamp_expired{};
ts::command_result_bulk result{};
result.emplace_result_n(cmd.bulkCount(), error::ok);
auto parse_result = parseTokenParameters(
result,
cmd,
token_description,
max_uses,
timestamp_expired,
actions
);
if(parse_result.has_error()) {
return parse_result;
}
parse_result.release_data();
/* filter actions for duplicates etc */
{
std::deque<token::TokenAction> current_actions{};
if(!this->server->getTokenManager().query_token_actions(token->id, current_actions)) {
return ts::command_result{error::vs_critical};
}
for(const auto& action : actions) {
if (action.type == ActionType::ActionDeleted) {
auto index = std::find_if(current_actions.begin(), current_actions.end(), [&](const auto& caction) { return caction.id == action.id; });
if(index != current_actions.end()) {
current_actions.erase(index);
}
}
}
size_t action_index{0};
for(auto& action : actions) {
switch(action.type) {
case ActionType::AddServerGroup:
case ActionType::RemoveServerGroup:
case ActionType::SetChannelGroup:
case ActionType::AllowChannelJoin: {
auto index = std::find_if(current_actions.begin(), current_actions.end(), [&](const TokenAction& caction) {
if(caction.type != action.type) {
return false;
}
if(caction.text != action.text) {
return false;
}
if(caction.id1 != action.id1) {
return false;
}
return caction.id2 == action.id2;
});
if(index != current_actions.end()) {
action.type = ActionType::ActionIgnore;
result.set_result(action_index, ts::command_result{error::database_no_modifications});
break;
}
current_actions.push_back(action);
break;
}
case ActionType::ActionSqlFailed:
case ActionType::ActionIgnore:
case ActionType::ActionDeleted:
default:
break;
}
action_index++;
}
}
check_token_action_permissions(this->ref(), result, actions);
if(token_description.has_value() || max_uses.has_value() || timestamp_expired.has_value()) {
token->description = token_description.value_or(token->description);
token->max_uses = max_uses.value_or(token->max_uses);
token->timestamp_expired = timestamp_expired.value_or(token->timestamp_expired);
this->server->getTokenManager().save_token(token);
}
if(!actions.empty()) {
size_t new_actions{0};
std::vector<token::TokenActionId> actions_removed{};
actions_removed.reserve(actions.size());
for(const auto& action : actions) {
if(action.type == ActionType::ActionDeleted) {
actions_removed.push_back(action.id);
} else if(action.type == ActionType::ActionIgnore) {
/* just ignore the action */
} else {
new_actions++;
}
}
if(!actions_removed.empty()) {
this->server->getTokenManager().delete_token_actions(token->id, actions_removed);
}
if(new_actions > 0) {
this->server->getTokenManager().add_token_actions(token->id, actions);
ts::command_builder notify{this->notify_response_command("notifytokenedit")};
for(size_t index{0}; index < actions.size(); index++) {
auto& action = actions[index];
if(action.type == token::ActionType::ActionIgnore || action.type == token::ActionType::ActionDeleted) {
continue;
} else if(action.type == token::ActionType::ActionSqlFailed) {
result.set_result(0, ts::command_result{error::database_constraint});
} else {
notify.put_unchecked(index, "action_id", action.id);
}
}
this->sendCommand(notify);
}
}
return command_result{std::move(result)};
}
command_result ConnectedClient::handleCommandTokenUse(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1);
//ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1);
auto strToken = cmd["token"].string();
auto token = this->server->tokenManager->findToken(strToken);
if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"};
this->server->tokenManager->deleteToke(token->token);
auto& token_manager = this->server->getTokenManager();
auto token = token_manager.load_token(cmd["token"], true);
if(!token) {
return ts::command_result{error::token_invalid_id};
}
if(token->is_expired()) {
return ts::command_result{error::token_expired};
}
if(token->use_limit_reached()) {
return ts::command_result{error::token_use_limit_exceeded};
}
auto serverGroup = this->server->groups->findGroup(token->groupId);
if (!serverGroup) return command_result{error::token_invalid_id, "Token invalid groupId"};
this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; //TODO test if its the default token
std::shared_ptr<BasicChannel> channel = nullptr;
if (token->channelId != 0) {
channel = this->server->channelTree->findChannel(token->channelId);
if (!channel) return command_result{error::token_invalid_id, "Token invalid channelId"};
this->server->groups->setChannelGroup(this->getClientDatabaseId(), serverGroup, channel);
} else {
if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), serverGroup))
this->server->groups->addServerGroup(this->getClientDatabaseId(), serverGroup);
else {
return command_result{error::ok};
}
}
if (this->server->notifyClientPropertyUpdates(this->ref(), this->server->groups->update_server_group_property(this->ref(), true, this->getChannel()))) {
this->task_update_needed_permissions.enqueue();
{
for (auto &viewer : this->server->getClients()) {
if(viewer->isClientVisible(this->ref(), true))
viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), serverGroup);
}
}
this->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), serverGroup);
}
token_manager.log_token_use(token->id);
this->useToken(token->id);
return command_result{error::ok};
}
@ -1121,24 +1601,33 @@ command_result ConnectedClient::handleCommandTokenDelete(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete, 1);
auto strToken = cmd["token"].string();
auto token = this->server->tokenManager->findToken(strToken);
if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"};
this->server->tokenManager->deleteToke(token->token);
std::shared_ptr<token::Token> token{};
if(cmd[0].has("token_id")) {
token = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as<token::TokenId>(), true);
} else if(cmd[0].has("token")) {
token = this->server->getTokenManager().load_token(cmd["token"], true);
}
if(!token) {
return ts::command_result{error::token_invalid_id};
}
if(token->issuer_database_id != this->getClientDatabaseId()) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete_all, 1);
}
this->server->getTokenManager().delete_token(token->id);
if(token->token == this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as<string>()) {
this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = "";
this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
logMessage(this->getServerId(), "{} Deleting the default server token. Don't ask anymore for this a token!", CLIENT_STR_LOG_PREFIX);
}
return command_result{error::ok};
}
//start=0 duration=10
//pattern=%asd%
command_result ConnectedClient::handleCommandPluginCmd(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(5);

View File

@ -48,7 +48,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};

View File

@ -35,7 +35,6 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
#define QUERY_PASSWORD_LENGTH 12
@ -584,6 +583,10 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) {
});
}
if(this->server) {
this->server->tokenManager->handle_server_group_deleted(serverGroup->groupId());
}
return command_result{error::ok};
}

View File

@ -696,11 +696,14 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
}
serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION);
size_t total_tokens{};
auto tokens = server->tokenManager->list_tokens(total_tokens, std::nullopt, { 1 }, std::nullopt);
Command res("");
res["sid"] = server->getServerId();
res["error"] = startError;
res["virtualserver_port"] = server->properties()[property::VIRTUALSERVER_PORT].as<string>();
res["token"] = server->tokenManager->avariableTokes().empty() ? "unknown" : server->tokenManager->avariableTokes()[0]->token;
res["token"] = tokens.empty() ? "unknown" : tokens[0]->token;
res["time_create"] = time_create.count();
res["time_start"] = time_start.count();
res["time_global"] = time_global.count();

View File

@ -520,12 +520,13 @@ void LicenseService::schedule_next_request(bool request_success) {
#endif
this->timings.next_request = this->timings.last_request + next_request;
if(this->verbose_)
if(this->verbose_) {
logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->timings.next_request));
}
}
void LicenseService::execute_tick() {
std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendeling */
std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendling */
if(!rlock) return;
/* do it not above because if we might have a deadlock here we don't want to punish the user */
@ -534,10 +535,11 @@ void LicenseService::execute_tick() {
if(std::chrono::steady_clock::now() - difference > this->startup_timepoint_) {
this->startup_timepoint_ = std::chrono::steady_clock::now(); /* shut down only once */
if(config::license->isPremium())
if(config::license->isPremium()) {
logCritical(LOG_INSTANCE, strobf("Failed to validate license within 4 days.").string());
else
} else {
logCritical(LOG_INSTANCE, strobf("Failed to validate instance integrity within 7 days.").string());
}
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
ts::server::shutdownInstance();
return;
@ -552,6 +554,8 @@ void LicenseService::execute_tick() {
return;
}
}
if(std::chrono::system_clock::now() > this->timings.next_request)
if(std::chrono::system_clock::now() > this->timings.next_request) {
this->begin_request();
}
}

View File

@ -44,8 +44,8 @@ if(!result && result.msg().find(ignore) == string::npos){
#define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")");
#define CURRENT_DATABASE_VERSION 17
#define CURRENT_PERMISSION_VERSION 8
#define CURRENT_DATABASE_VERSION 18
#define CURRENT_PERMISSION_VERSION 9
#define CLIENT_UID_LENGTH "64"
#define CLIENT_NAME_LENGTH "128"
@ -578,6 +578,54 @@ bool SqlDataManager::update_database(std::string &error) {
db_version(17);
}
case 17: {
constexpr static std::array<std::string_view, 11> kUpdateCommands{
"CREATE TABLE tokens_new("
" `token_id` INTEGER NOT NULL PRIMARY KEY [AUTO_INCREMENT],"
" `server_id` INT,"
" `token` VARCHAR(32),"
" `description` VARCHAR(255),"
" `issuer_database_id` BIGINT,"
" `max_uses` INT,"
" `use_count` INT DEFAULT 0,"
" `timestamp_created` INT,"
" `timestamp_expired` INT"
");",
"CREATE TABLE token_actions("
" `action_id` INTEGER NOT NULL PRIMARY KEY [AUTO_INCREMENT],"
" `server_id` INTEGER NOT NULL,"
" `token_id` INTEGER NOT NULL,"
" `type` INTEGER,"
" `id1` BIGINT,"
" `id2` BIGINT,"
" `text` TEXT"
");",
"INSERT INTO `tokens_new`(`server_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `timestamp_created`, `timestamp_expired`)"
" SELECT `serverId`, `token`, `description`, 0, 1, `created`, 0 FROM `tokens`;",
"INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`)"
" SELECT `serverId`, `token_id`, 1, `targetGroup`, 0, \"\" FROM `tokens` LEFT JOIN `tokens_new` ON `tokens_new`.`token` = `tokens`.`token` WHERE `type` = 0;",
"INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`)"
" SELECT `serverId`, `token_id`, 3, `targetGroup`, `targetChannel`, \"\" FROM `tokens` LEFT JOIN `tokens_new` ON `tokens_new`.`token` = `tokens`.`token` WHERE `type` = 1;",
"DROP TABLE `tokens`;",
"ALTER TABLE `tokens_new` RENAME TO `tokens`;",
"CREATE UNIQUE INDEX `tokens_token` ON `tokens` (`token`);",
"CREATE INDEX `tokens_client` ON `tokens` (`issuer_database_id`);",
"CREATE INDEX `tokens_server` ON `tokens` (`server_id`);",
"CREATE INDEX `token_actions_token_id` ON `token_actions` (`token_id`);",
};
if(!execute_commands(this->sql(), error, kUpdateCommands))
return false;
db_version(18);
}
default:
break;
}
@ -849,26 +897,26 @@ if(!auto_update(permission::update::type, name, {value, value != permNotGranted}
do_auto_update(QUERY_ADMIN, "i_video_max_screen_streams", permNotGranted, 100);
do_auto_update(QUERY_ADMIN, "i_video_max_camera_streams", permNotGranted, 100);
do_auto_update(SERVER_ADMIN, "b_video_screen", 1, 100);
do_auto_update(SERVER_ADMIN, "b_video_camera", 1, 100);
do_auto_update(SERVER_ADMIN, "i_video_max_kbps", 20 * 1000 * 1000, 100);
do_auto_update(SERVER_ADMIN, "i_video_max_streams", permNotGranted, 100);
do_auto_update(SERVER_ADMIN, "i_video_max_screen_streams", permNotGranted, 100);
do_auto_update(SERVER_ADMIN, "i_video_max_camera_streams", permNotGranted, 100);
do_auto_update(SERVER_ADMIN, "b_video_screen", 1, 75);
do_auto_update(SERVER_ADMIN, "b_video_camera", 1, 75);
do_auto_update(SERVER_ADMIN, "i_video_max_kbps", 20 * 1000 * 1000, 75);
do_auto_update(SERVER_ADMIN, "i_video_max_streams", permNotGranted, 75);
do_auto_update(SERVER_ADMIN, "i_video_max_screen_streams", permNotGranted, 75);
do_auto_update(SERVER_ADMIN, "i_video_max_camera_streams", permNotGranted, 75);
do_auto_update(SERVER_NORMAL, "b_video_screen", 1, 100);
do_auto_update(SERVER_NORMAL, "b_video_camera", 1, 100);
do_auto_update(SERVER_NORMAL, "i_video_max_kbps", 5 * 1000 * 1000, 100);
do_auto_update(SERVER_NORMAL, "i_video_max_streams", 20, 100);
do_auto_update(SERVER_NORMAL, "i_video_max_screen_streams", 2, 100);
do_auto_update(SERVER_NORMAL, "i_video_max_camera_streams", 20, 100);
do_auto_update(SERVER_NORMAL, "b_video_screen", 1, permNotGranted);
do_auto_update(SERVER_NORMAL, "b_video_camera", 1, permNotGranted);
do_auto_update(SERVER_NORMAL, "i_video_max_kbps", 5 * 1000 * 1000, permNotGranted);
do_auto_update(SERVER_NORMAL, "i_video_max_streams", 20, permNotGranted);
do_auto_update(SERVER_NORMAL, "i_video_max_screen_streams", 2, permNotGranted);
do_auto_update(SERVER_NORMAL, "i_video_max_camera_streams", 20, permNotGranted);
do_auto_update(SERVER_GUEST, "b_video_screen", 1, 100);
do_auto_update(SERVER_GUEST, "b_video_camera", 1, 100);
do_auto_update(SERVER_GUEST, "i_video_max_kbps", 2500 * 1000, 100);
do_auto_update(SERVER_GUEST, "i_video_max_streams", 8, 100);
do_auto_update(SERVER_GUEST, "i_video_max_screen_streams", 1, 100);
do_auto_update(SERVER_GUEST, "i_video_max_camera_streams", 8, 100);
do_auto_update(SERVER_GUEST, "b_video_screen", 1, permNotGranted);
do_auto_update(SERVER_GUEST, "b_video_camera", 1, permNotGranted);
do_auto_update(SERVER_GUEST, "i_video_max_kbps", 2500 * 1000, permNotGranted);
do_auto_update(SERVER_GUEST, "i_video_max_streams", 8, permNotGranted);
do_auto_update(SERVER_GUEST, "i_video_max_screen_streams", 1, permNotGranted);
do_auto_update(SERVER_GUEST, "i_video_max_camera_streams", 8, permNotGranted);
perm_version(7);
@ -880,6 +928,23 @@ if(!auto_update(permission::update::type, name, {value, value != permNotGranted}
}
perm_version(8);
case 8:
do_auto_update(QUERY_ADMIN, "b_virtualserver_token_list_all", 1, 100);
do_auto_update(SERVER_ADMIN, "b_virtualserver_token_list_all", 1, 75);
do_auto_update(QUERY_ADMIN, "b_virtualserver_token_edit_all", 1, 100);
do_auto_update(SERVER_ADMIN, "b_virtualserver_token_edit_all", 1, 75);
do_auto_update(QUERY_ADMIN, "b_virtualserver_token_delete_all", 1, 100);
do_auto_update(SERVER_ADMIN, "b_virtualserver_token_delete_all", 1, 75);
do_auto_update(QUERY_ADMIN, "i_virtualserver_token_limit", -1, 100);
do_auto_update(SERVER_ADMIN, "i_virtualserver_token_limit", -1, 75);
do_auto_update(SERVER_NORMAL, "i_virtualserver_token_limit", 100, permNotGranted);
do_auto_update(SERVER_GUEST, "i_virtualserver_token_limit", 50, permNotGranted);
perm_version(9);
default:
break;
}

View File

@ -3,126 +3,348 @@
//
#include <algorithm>
#include <sqlite3.h>
#include <iostream>
#include <random>
#include <utility>
#include <misc/rnd.h>
#include <log/LogUtils.h>
#include "TokeManager.h"
#include "src/VirtualServer.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::token;
TokenManager::TokenManager(server::VirtualServer* handle) : handle(handle) {
TokenManager::TokenManager(sql::SqlManager *sql, ServerId server_id) : sql_manager{sql}, virtual_server_id{server_id} {}
TokenManager::~TokenManager() {}
void TokenManager::initialize_cache() {}
void TokenManager::finalize_cache() {}
#define TOKEN_LOAD_PROPERTIES "`token_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `use_count`, `timestamp_created`, `timestamp_expired`"
#define TOKEN_USE_ABLE "(`max_uses` = 0 OR `max_uses` > `use_count`) AND (`timestamp_expired` = 0 OR `timestamp_expired` > :now)"
void TokenManager::load_tokens(std::deque<std::shared_ptr<Token>> &result, sql::command &command) {
command.query([&](int length, std::string* values, std::string* names) {
auto index{0};
auto& token = result.emplace_back();
token = std::make_shared<Token>();
try {
assert(names[index] == "token_id");
token->id = std::stoull(values[index++]);
assert(names[index] == "token");
token->token = values[index++];
assert(names[index] == "description");
token->description = values[index++];
assert(names[index] == "issuer_database_id");
token->issuer_database_id = std::stoull(values[index++]);
assert(names[index] == "max_uses");
token->max_uses = std::stoull(values[index++]);
assert(names[index] == "use_count");
token->use_count = std::stoull(values[index++]);
assert(names[index] == "timestamp_created");
token->timestamp_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
assert(names[index] == "timestamp_expired");
token->timestamp_expired = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
assert(index == length);
} catch (std::exception& ex) {
result.pop_back();
logError(this->virtual_server_id, "Failed to parse token at index {}: {}",
index - 1,
ex.what()
);
return;
}
});
}
TokenManager::~TokenManager() = default;
size_t TokenManager::client_token_count(ClientDbId client) {
sql::command command{this->sql_manager, "SELECT COUNT(`token_id`) FROM `tokens` WHERE `server_id` = :server_id AND `issuer_database_id` = :client AND " TOKEN_USE_ABLE};
command.value(":server_id", this->virtual_server_id);
command.value(":client", client);
command.value(":now", std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
bool TokenManager::loadTokens() {
LOG_SQL_CMD(sql::command(handle->getSql(), "SELECT * FROM `tokens` WHERE `serverId` = :sid", variable{":sid", handle->getServerId()}).query(&TokenManager::loadTokenFromDb, this));
return true;
}
size_t total_count{(size_t) -1};
auto query_result = command.query([&](int, std::string* values, std::string*) {
total_count = std::stoull(values[0]);
});
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
std::string token, description;
auto type = static_cast<TokenType>(0xFF);
GroupId group = 0;
ChannelId chId = 0;
time_point<system_clock> created;
for(int i = 0; i < length; i++){
if(strcmp(columns[i], "type") == 0)
type = static_cast<TokenType>(stoi(values[i]));
else if(strcmp(columns[i], "token") == 0)
token = values[i];
else if(strcmp(columns[i], "targetGroup") == 0)
group = static_cast<GroupId>(stoll(values[i]));
else if(strcmp(columns[i], "targetChannel") == 0)
chId = static_cast<ChannelId>(stoll(values[i]));
else if(strcmp(columns[i], "description") == 0)
description = values[i];
else if(strcmp(columns[i], "created") == 0)
created = time_point<system_clock>() + seconds(stoll(values[i]));
else if(strcmp(columns[i], "serverId") == 0);
else cerr << "Invalid column id `" << columns[i] << "`" << endl;
if(!query_result) {
LOG_SQL_CMD(query_result);
}
assert(!token.empty());
//assert(!description.empty());
assert(type != 0xFF);
assert(group != 0);
assert(created.time_since_epoch().count() != 0);
return total_count;
}
auto token_entry = std::make_shared<TokenEntry>(token, type, group, chId, system_clock::now(), description);
std::deque<std::shared_ptr<Token>> TokenManager::list_tokens(size_t &total_count, const std::optional<size_t> &offset,
const std::optional<size_t> &limit, const std::optional<ClientDbId>& owner) {
{
std::lock_guard tlock{this->token_lock};
this->tokens.push_back(token_entry);
std::string sql_command{};
sql_command += "SELECT COUNT(`token_id`) FROM `tokens` WHERE `server_id` = :server_id AND " TOKEN_USE_ABLE;
if(owner.has_value()) {
sql_command += " AND `issuer_database_id` = :owner";
}
sql::command command{this->sql_manager, sql_command};
command.value(":server_id", this->virtual_server_id);
command.value(":owner", owner.value_or(0));
command.value(":now", std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
auto query_result = command.query([&](int, std::string* values, std::string*) {
total_count = std::stoull(values[0]);
});
if(!query_result) {
LOG_SQL_CMD(query_result);
total_count = 0;
}
}
return 0;
std::deque<std::shared_ptr<Token>> result{};
{
std::string sql_command{};
sql_command += "SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `server_id` = :server_id AND " TOKEN_USE_ABLE;
if(owner.has_value()) {
sql_command += " AND `issuer_database_id` = :owner";
}
sql_command += " LIMIT :offset, :limit";
sql::command command{this->sql_manager, sql_command};
command.value(":server_id", this->virtual_server_id);
command.value(":owner", owner.value_or(0));
command.value(":offset", offset.value_or(0));
command.value(":limit", limit.has_value() ? (int64_t) *limit : -1);
command.value(":now", std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
this->load_tokens(result, command);
}
return result;
}
std::shared_ptr<TokenEntry> TokenManager::createToken(TokenType type, GroupId group, const std::string& description, ChannelId chid, std::string token) {
token = token.empty() ? rnd_string(20) : token;
std::shared_ptr<Token> TokenManager::load_token(const std::string &token, bool ignore_limits) {
std::deque<std::shared_ptr<Token>> result{};
shared_ptr<TokenEntry> entry = make_shared<TokenEntry>(token, type, group, chid, system_clock::now(), description);
{
std::lock_guard tlock{this->token_lock};
this->tokens.push_back(entry);
std::string sql_command{"SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `token` = :token"};
if(!ignore_limits) {
sql_command += " AND " TOKEN_USE_ABLE;
}
auto res = sql::command(handle->getSql(), "INSERT INTO `tokens` (`serverId`, `type`, `token`, `targetGroup`, `targetChannel`, `description`, `created`) VALUES (:sid, :type, :token, :targetGroup, :targetChannel, :desc, :created);",
variable{":sid", this->handle->getServerId()},
variable{":type", entry->type},
variable{":token", entry->token},
variable{":targetGroup", entry->groupId},
variable{":targetChannel", entry->channelId},
variable{":desc", entry->description},
variable{":created", duration_cast<seconds>(entry->created.time_since_epoch()).count()}
).execute();
auto pf = LOG_SQL_CMD;
pf(res);
sql::command command{this->sql_manager, sql_command};
command.value(":token", token);
command.value(":now", std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
this->load_tokens(result, command);
return entry;
return result.empty() ? nullptr : result.front();
}
bool TokenManager::deleteToke(const std::string& str) {
std::shared_ptr<TokenEntry> token;
{
std::lock_guard tlock{this->token_lock};
auto it = std::find_if(this->tokens.begin(), this->tokens.end(), [&](const auto& token) { return token->token == str; });
if(it == this->tokens.end())
return false;
token = std::move(*it);
this->tokens.erase(it);
std::shared_ptr<Token> TokenManager::load_token_by_id(TokenId token_id, bool ignore_limits) {
std::deque<std::shared_ptr<Token>> result{};
std::string sql_command{"SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `token_id` = :token_id"};
if(!ignore_limits) {
sql_command += " AND " TOKEN_USE_ABLE;
}
auto res = sql::command(handle->getSql(), "DELETE FROM `tokens` WHERE `token` = :token", variable{":token", token->token}).execute();
auto pf = LOG_SQL_CMD;
pf(res);
sql::command command{this->sql_manager, sql_command};
command.value(":token_id", token_id);
command.value(":now", std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
this->load_tokens(result, command);
return result.empty() ? nullptr : result.front();
}
std::shared_ptr<Token> TokenManager::create_token(
ClientDbId issuer_database_id,
const std::string &description,
size_t max_uses,
const std::chrono::system_clock::time_point &timestamp_expired
) {
auto token = rnd_string(32);
auto created_timestamp = std::chrono::system_clock::now();
sql::command command{this->sql_manager, "INSERT INTO `tokens`(`server_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `timestamp_created`, `timestamp_expired`)"
" VALUES (:server_id, :token, :description, :issuer_database_id, :max_uses, :timestamp_created, :timestamp_expired);"};
command.value(":server_id", this->virtual_server_id);
command.value(":token", token);
command.value(":description", description);
command.value(":issuer_database_id", issuer_database_id);
command.value(":max_uses", max_uses);
command.value(":timestamp_created", std::chrono::floor<std::chrono::seconds>(created_timestamp.time_since_epoch()).count());
command.value(":timestamp_expired", std::chrono::floor<std::chrono::seconds>(timestamp_expired.time_since_epoch()).count());
auto insert_result = command.execute();
if(!insert_result) {
logError(this->virtual_server_id, "Failed to register new token: {}", insert_result.fmtStr());
return nullptr;
}
auto result = std::make_shared<Token>();
result->id = insert_result.last_insert_rowid();
result->token = token;
result->issuer_database_id = issuer_database_id;
result->description = description;
result->max_uses = max_uses;
result->use_count = 0;
result->timestamp_expired = timestamp_expired;
result->timestamp_created = created_timestamp;
return result;
}
bool TokenManager::query_token_actions(TokenId token_id, std::deque<TokenAction> &actions) {
sql::command command{this->sql_manager,
"SELECT `action_id`, `type`, `id1`, `id2`, `text` FROM `token_actions` WHERE `token_id` = :token_id"};
command.value(":token_id", token_id);
command.query([&](int length, std::string* values, std::string* names) {
auto index{0};
auto& action = actions.emplace_back();
try {
assert(names[index] == "action_id");
action.id = std::stoull(values[index++]);
assert(names[index] == "type");
action.type = (ActionType) std::stoull(values[index++]);
assert(names[index] == "id1");
action.id1 = std::stoull(values[index++]);
assert(names[index] == "id2");
action.id2 = std::stoull(values[index++]);
assert(names[index] == "text");
action.text = values[index++];
assert(index == length);
} catch (std::exception& ex) {
actions.pop_back();
logError(this->virtual_server_id, "Failed to parse token action at index {}: {}",
index - 1,
ex.what()
);
return;
}
});
return true;
}
void TokenManager::handleGroupDelete(GroupId group_id) {
std::lock_guard tlock{this->token_lock};
this->tokens.erase(std::remove_if(this->tokens.begin(), this->tokens.end(), [&](const std::shared_ptr<TokenEntry>& token) {
return token->groupId == group_id;
}), this->tokens.end());
void TokenManager::add_token_actions(TokenId token_id, std::vector<TokenAction> &actions) {
sql::model insert{this->sql_manager, "INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`) VALUES (:server_id, :token_id, :type, :id1, :id2, :text)"};
insert.value(":server_id", this->virtual_server_id);
insert.value(":token_id", token_id);
for(auto& action : actions) {
if(action.type == ActionType::ActionDeleted || action.type == ActionType::ActionSqlFailed || action.type == ActionType::ActionIgnore) {
continue;
}
auto command = insert.command();
command.value(":type", (uint8_t) action.type);
command.value(":id1", action.id1);
command.value(":id2", action.id2);
command.value(":text", action.text);
auto result = command.execute();
if(!result) {
logError(this->virtual_server_id, "Failed to insert token action {} for token id {}: {}", (uint32_t) action.type, token_id, result.fmtStr());
action.type = ActionType::ActionSqlFailed;
} else {
action.id = result.last_insert_rowid();
}
}
}
std::shared_ptr<TokenEntry> TokenManager::findToken(const std::string& str) {
std::lock_guard tlock{this->token_lock};
for(const auto& elm : this->tokens)
if(elm->token == str)
return elm;
return nullptr;
void TokenManager::delete_token_actions(TokenId, const std::vector<TokenActionId> &actions) {
if(actions.empty()) {
return;
}
std::string sql_command{};
sql_command += "DELETE FROM `token_actions` WHERE `action_id` IN (";
for(size_t index{0}; index < actions.size(); index++) {
if(index > 0) {
sql_command += ", ";
}
sql_command += std::to_string(actions[index]);
}
sql_command += ");";
sql::command command{this->sql_manager, sql_command};
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete token actions"});
}
TokenEntry::TokenEntry(string token, TokenType type, GroupId groupId, ChannelId channelId, const time_point<system_clock> &created, string description) : token(std::move(token)), type(type), groupId(groupId), channelId(channelId),
created(created), description(std::move(description)) {}
void TokenManager::save_token(const std::shared_ptr<Token> &token) {
sql::command command{this->sql_manager, "UPDATE `tokens` SET `description` = :description, `max_uses` = :max_uses, `timestamp_expired` = :timestamp_expired WHERE `token_id` = :token_id;"};
command.value(":token_id", token->id);
command.value(":description", token->description);
command.value(":max_uses", token->max_uses);
command.value(":timestamp_expired", std::chrono::floor<std::chrono::seconds>(token->timestamp_expired.time_since_epoch()).count());
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed update token"});
}
void TokenManager::delete_token(TokenId token_id) {
{
sql::command command{this->sql_manager, "DELETE FROM `tokens` WHERE `token_id` = :token_id;"};
command.value(":token_id", token_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed delete token"});
}
{
sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `token_id` = :token_id;"};
command.value(":token_id", token_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed delete token actions"});
}
}
void TokenManager::log_token_use(TokenId token_id) {
sql::command command{this->sql_manager, "UPDATE `tokens` SET `use_count` = `use_count` + 1 WHERE `token_id` = :token_id;"};
command.value(":token_id", token_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to log toke use"});
}
void TokenManager::handle_channel_deleted(ChannelId channel_id) {
sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id2` = :id2;"};
command.value(":server_id", this->virtual_server_id);
command.value(":type", (uint8_t) ActionType::SetChannelGroup);
command.value(":id2", channel_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted channel"});
}
void TokenManager::handle_channel_group_deleted(GroupId group_id) {
sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"};
command.value(":server_id", this->virtual_server_id);
command.value(":type", (uint8_t) ActionType::SetChannelGroup);
command.value(":id1", group_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted channel group"});
}
void TokenManager::handle_server_group_deleted(GroupId group_id) {
{
sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"};
command.value(":server_id", this->virtual_server_id);
command.value(":type", (uint8_t) ActionType::AddServerGroup);
command.value(":id1", group_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted server group"});
}
{
sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"};
command.value(":server_id", this->virtual_server_id);
command.value(":type", (uint8_t) ActionType::RemoveServerGroup);
command.value(":id1", group_id);
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted server group"});
}
}

View File

@ -4,48 +4,147 @@
#include <string>
#include <vector>
#include <memory>
#include <sql/SqlQuery.h>
#include "Definitions.h"
namespace ts {
namespace server {
class VirtualServer;
}
namespace ts::server::token {
typedef uint64_t TokenId;
typedef uint64_t TokenActionId;
namespace token {
enum TokenType : uint8_t {
TOKEN_SERVER,
TOKEN_CHANNEL
};
struct TokenEntry {
TokenEntry(std::string token, TokenType type, GroupId groupId, ChannelId channelId, const std::chrono::time_point<std::chrono::system_clock> &created, std::string description);
enum struct ActionType {
/**
* Add a server group to the client.
* `id1` will contain the server group id.
*/
AddServerGroup = 0x01,
/**
* Remove a server group from the client (if he is assigned to it)
* `id1` will contain the server group id.
*/
RemoveServerGroup = 0x02,
/**
* Set the channel group of the client for a channel.
* `id1` will contain the channel group id.
* `id2` will contain the target channel id
*/
SetChannelGroup = 0x03,
/**
* Allow the client to join (on server join) to a certain channel.
* `id2` will contain the target channel id
*/
AllowChannelJoin = 0x04,
std::string token;
TokenType type; //token_type
GroupId groupId; //token_id1
ChannelId channelId; //token_id2
std::chrono::time_point<std::chrono::system_clock> created;
std::string description;
};
/**
* Sentinel for internal handling
*/
ActionSqlFailed = 0xFD,
ActionDeleted = 0xFE,
ActionIgnore = 0xFF
};
class TokenManager {
public:
explicit TokenManager(server::VirtualServer*);
~TokenManager();
bool loadTokens();
struct Token {
TokenId id{0};
std::vector<std::shared_ptr<TokenEntry>> avariableTokes(){ return tokens; }
std::shared_ptr<TokenEntry> findToken(const std::string&);
std::shared_ptr<TokenEntry> createToken(TokenType, GroupId, const std::string&, ChannelId = 0, std::string token = "");
bool deleteToke(const std::string&);
std::string token{};
std::string description{};
void handleGroupDelete(GroupId);
private:
int loadTokenFromDb(int length, char** values, char** columns);
server::VirtualServer* handle;
ClientDbId issuer_database_id{0};
std::mutex token_lock{};
std::vector<std::shared_ptr<TokenEntry>> tokens;
};
}
size_t max_uses{0};
size_t use_count{0};
std::chrono::system_clock::time_point timestamp_expired{};
std::chrono::system_clock::time_point timestamp_created{};
[[nodiscard]] inline bool is_expired() const {
return this->timestamp_expired.time_since_epoch().count() > 0 && std::chrono::system_clock::now() >= this->timestamp_expired;
}
[[nodiscard]] inline bool use_limit_reached() const {
return this->max_uses > 0 && this->use_count >= this->max_uses;
}
};
struct TokenAction {
TokenActionId id{0};
ActionType type;
uint64_t id1{0};
uint64_t id2{0};
std::string text{};
};
class TokenManager {
public:
explicit TokenManager(sql::SqlManager*, ServerId);
~TokenManager();
void initialize_cache();
void finalize_cache();
[[nodiscard]] std::shared_ptr<Token> load_token(const std::string& /* token */, bool /* ignore limits */);
[[nodiscard]] std::shared_ptr<Token> load_token_by_id(TokenId /* token id */, bool /* ignore limits */);
void log_token_use(TokenId /* token id */);
/**
* Create a new token
* @returns `nullptr` if some database errors occurred else the generated token.
*/
[[nodiscard]] std::shared_ptr<Token> create_token(
ClientDbId /* issuer */,
const std::string& /* description */,
size_t /* max uses */,
const std::chrono::system_clock::time_point& /* expire timestamp */
);
/**
* List all tokens
* @return
*/
[[nodiscard]] std::deque<std::shared_ptr<Token>> list_tokens(
size_t& /* total tokens */,
const std::optional<size_t>& /* offset */,
const std::optional<size_t>& /* limit */,
const std::optional<ClientDbId>& /* owner */
);
[[nodiscard]] size_t client_token_count(ClientDbId /* client */);
/**
* If the operation succeeds the new action id will be applied to the token.
* Note: Actions set to `ActionType::ActionDelete` or `ActionType::ActionIgnore` will be ignored.
* If the sql insert failed the type will be set to ActionSqlFailed
*/
void add_token_actions(TokenId /* token */, std::vector<TokenAction>& /* actions */);
/**
* Query all actions related to the token.
*/
[[nodiscard]] bool query_token_actions(TokenId /* token */, std::deque<TokenAction>& /* result */);
/**
* Delete an action from the token.
*/
void delete_token_actions(TokenId /* token */, const std::vector<TokenActionId>& /* actions */);
/**
* Save any modifications made to the token.
* Editable fields:
* - `description`
* - `max_uses`
* - `timestamp_expired`
*/
void save_token(const std::shared_ptr<Token>& /* token */);
void delete_token(TokenId /* token id */);
void handle_server_group_deleted(GroupId);
void handle_channel_group_deleted(GroupId);
void handle_channel_deleted(ChannelId);
private:
ServerId virtual_server_id;
sql::SqlManager* sql_manager;
void load_tokens(std::deque<std::shared_ptr<Token>>& /* result */, sql::command& /* command */);
};
}
DEFINE_VARIABLE_TRANSFORM(ts::token::TokenType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast<ts::token::TokenType>(in.as<uint8_t>()));
DEFINE_VARIABLE_TRANSFORM_ENUM(ts::server::token::ActionType, uint8_t);

View File

@ -72,6 +72,9 @@ namespace ts {
}
inline void push_dg_write_queue(server::udp::DatagramPacket* packet) {
assert(!packet->next_packet);
packet->next_packet = nullptr;
std::lock_guard lock(this->write_queue_lock);
if(this->dg_write_queue_tail) {
this->dg_write_queue_tail->next_packet = packet;