diff --git a/server/main.cpp b/server/main.cpp index c9c822d..4cc3072 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -421,7 +421,7 @@ int main(int argc, char** argv) { { rlimit rlimit{0, 0}; //forum.teaspeak.de/index.php?threads/2570/ - constexpr auto seek_help_message = "Fore more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/)."; + constexpr auto seek_help_message = "For more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/)."; if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) { //prlimit -n4096 -p pid_of_process logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno)); diff --git a/server/src/PermissionCalculator.cpp b/server/src/PermissionCalculator.cpp index 3e7a4bb..4ff66e6 100644 --- a/server/src/PermissionCalculator.cpp +++ b/server/src/PermissionCalculator.cpp @@ -84,6 +84,16 @@ void ClientPermissionCalculator::initialize_client(DataClient* client) { } } +PermissionFlaggedValue ClientPermissionCalculator::calculate_permission(permission::PermissionType permission, + bool granted) { + auto result = this->calculate_permissions({permission}, granted); + if(result.empty()) { + return { permNotGranted, false }; + } + + return result.front().second; +} + std::vector> ClientPermissionCalculator::calculate_permissions( const std::deque &permissions, bool calculate_granted @@ -328,7 +338,8 @@ const std::shared_ptr& ClientPermissionCalculator::assigne return *this->assigned_channel_group_; } - auto channel_group_assignment = this->group_manager_->assignments().channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->client_database_id, this->channel_id_); + auto channel_group_assignment = this->group_manager_->assignments().exact_channel_group_of_client( + groups::GroupAssignmentCalculateMode::GLOBAL, this->client_database_id, this->channel_id_); if(!channel_group_assignment.has_value()) { return *this->assigned_channel_group_; } diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 3bf61b2..109a569 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -8,6 +8,7 @@ #include #include #include "InstanceHandler.h" +#include "./groups/GroupManager.h" using namespace std; using namespace ts::server; @@ -264,33 +265,6 @@ void VirtualServer::testBanStateChange(const std::shared_ptr& i }); } -bool VirtualServer::could_default_create_channel() { - { - - auto default_group = this->group_manager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER); - if(default_group) { - auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1; - flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1; - flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1; - if(flag) - return true; - } - } - { - - auto default_group = this->group_manager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); - if(default_group) { - auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1; - flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1; - flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1; - if(flag) - return true; - } - } - - return false; -} - void VirtualServer::notify_client_ban(const shared_ptr &target, const std::shared_ptr &invoker, const std::string &reason, size_t time) { /* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */ lock_guard command_lock(target->command_lock); @@ -394,11 +368,13 @@ void VirtualServer::delete_channel(shared_ptr channel, const command_locks.push_back(move(unique_lock(client->command_lock))); } - for(const auto& client : clients) + for(const auto& client : clients) { this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock); + } - if(!tree_lock.owns_lock()) + if(!tree_lock.owns_lock()) { tree_lock.lock(); /* no clients left within that tree */ + } command_locks.clear(); auto deleted_channels = this->channelTree->delete_channel_root(channel); @@ -412,7 +388,25 @@ void VirtualServer::delete_channel(shared_ptr channel, const client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker); }); - this->tokenManager->handle_channel_deleted(channel->channelId()); + { + std::vector deleted_channel_ids{}; + deleted_channel_ids.reserve(deleted_channels.size()); + for(const auto& deleted_channel : deleted_channels) { + deleted_channel_ids.push_back(deleted_channel->channelId()); + } + + auto ref_self = this->ref(); + task_id task_id{}; + serverInstance->general_task_executor()->schedule(task_id, "database cleanup after channel delete", [ref_self, deleted_channel_ids]{ + for(const auto& deleted_channel_id : deleted_channel_ids) { + ref_self->tokenManager->handle_channel_deleted(deleted_channel_id); + } + + for(const auto& deleted_channel_id : deleted_channel_ids) { + ref_self->group_manager()->assignments().handle_channel_deleted(deleted_channel_id); + } + }); + } } void VirtualServer::client_move( @@ -450,10 +444,6 @@ void VirtualServer::client_move( s_target_channel = dynamic_pointer_cast(target_channel); assert(s_target_channel); } - - /* update the group properties here already, so for all enter views we could just send the new props directly */ - changed_groups = this->groups->update_server_group_property(target, true, s_target_channel); - client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter? } auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr; auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr; @@ -560,12 +550,7 @@ void VirtualServer::client_move( if (s_source_channel) { s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); - auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false); - if(source_channel_group) { - auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent); - if(!default_data.has_value || default_data.value != 1) - this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel); - } + this->group_manager()->assignments().cleanup_channel_temporary_assignment(target->getClientDatabaseId(), s_source_channel->channelId()); auto update = target->properties()[property::CLIENT_IS_TALKER].as_or(false) || target->properties()[property::CLIENT_TALK_REQUEST].as_or(0) > 0; @@ -582,15 +567,18 @@ void VirtualServer::client_move( if (s_target_channel) { target->task_update_needed_permissions.enqueue(); + target->task_update_displayed_groups.enqueue(); TIMING_STEP(timings, "perm gr upd"); if(s_source_channel) { deque deleted; - for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel)) + for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel)) { deleted.push_back(channel->channelId()); + } - if(!deleted.empty()) + if(!deleted.empty()) { target->notifyChannelHide(deleted, false); + } auto i_source_channel = s_source_channel->channelId(); if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) { diff --git a/server/src/TS3ServerHeartbeat.cpp b/server/src/TS3ServerHeartbeat.cpp index 59f0f00..3e87836 100644 --- a/server/src/TS3ServerHeartbeat.cpp +++ b/server/src/TS3ServerHeartbeat.cpp @@ -6,6 +6,7 @@ #include "VirtualServer.h" #include "./manager/ConversationManager.h" #include "./music/MusicBotManager.h" +#include "./groups/GroupManager.h" using namespace std; using namespace std::chrono; @@ -234,18 +235,7 @@ void VirtualServer::executeServerTick() { { BEGIN_TIMINGS(); - - auto groups = this->group_manager()->availableGroups(false); - for(auto& group : groups) { - auto permissions = group->permissions(); - if(permissions->require_db_updates()) { - auto begin = system_clock::now(); - serverInstance->databaseHelper()->saveGroupPermissions(this->getServerId(), group->groupId(), 0, permissions); - auto end = system_clock::now(); - debugMessage(this->serverId, "Saved group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast(end - begin).count()); - } - } - + this->group_manager()->save_permissions(); END_TIMINGS(timing_groups); } diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index f0c2a7f..5f9c62c 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include #include #include "./groups/GroupManager.h" +#include "./PermissionCalculator.h" using namespace std; using namespace std::chrono; @@ -364,13 +364,35 @@ bool VirtualServer::initialize(bool test_properties) { /* lets cleanup the conversations for not existent channels */ this->conversation_manager_->synchronize_channels(); + + { + auto ref_self = this->self; + this->task_notify_channel_group_list = multi_shot_task{serverInstance->general_task_executor(), "server notify channel group list", [ref_self]{ + auto this_ = ref_self.lock(); + if(this_) { + this_->forEachClient([](const std::shared_ptr& client) { + std::optional generated_notify{}; + client->notifyChannelGroupList(generated_notify, true); + }); + } + }}; + this->task_notify_server_group_list = multi_shot_task{serverInstance->general_task_executor(), "server notify server group list", [ref_self]{ + auto this_ = ref_self.lock(); + if(this_) { + this_->forEachClient([](const std::shared_ptr& client) { + std::optional generated_notify{}; + client->notifyServerGroupList(generated_notify, true); + }); + } + }}; + } + return true; } VirtualServer::~VirtualServer() { memtrack::freed(this); delete this->tokenManager; - delete this->groups; delete this->channelTree; delete this->letters; delete this->complains; @@ -653,10 +675,6 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) { this->webControlServer = nullptr; #endif - if(this->groups) { - this->groups->clearCache(); - } - properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = 0; properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = 0; properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = 0; @@ -778,10 +796,12 @@ std::shared_ptr VirtualServer::findClient(std::string name, boo bool VirtualServer::forEachClient(std::function)> function) { for(const auto& elm : this->getClients()) { shared_lock close_lock(elm->finalDisconnectLock, try_to_lock_t{}); - if(close_lock.owns_lock()) //If not locked than client is on the way to disconnect + if(close_lock.owns_lock()) { + //If not locked than client is on the way to disconnect if(elm->state == ConnectionState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL) { function(elm); } + } } return true; } @@ -883,32 +903,6 @@ void VirtualServer::broadcastMessage(std::shared_ptr invoker, s }); } -std::vector> CalculateCache::getGroupAssignments(VirtualServer* server, ClientDbId cldbid, ClientType type) { - if(assignment_server_groups_set) return assignment_server_groups; - - assignment_server_groups = server->group_manager()->getServerGroups(cldbid, type); - assignment_server_groups_set = true; - return assignment_server_groups; -} - -std::shared_ptr CalculateCache::getChannelAssignment(VirtualServer* server, ClientDbId client_dbid, ChannelId channel_id) { - if(this->assignment_channel_group_set && this->assignment_channel_group_channel == channel_id) return this->assignment_channel_group; - - auto channel = this->getServerChannel(server, channel_id); - assignment_channel_group = channel ? server->group_manager()->getChannelGroup(client_dbid, channel, true) : nullptr; - assignment_channel_group_set = true; - assignment_channel_group_channel = channel_id; - return assignment_channel_group; -} - -std::shared_ptr CalculateCache::getServerChannel(ts::server::VirtualServer *server, ts::ChannelId channel_id) { - if(this->last_server_channel == channel_id) - return this->server_channel; - this->last_server_channel = channel_id; - this->server_channel = server && channel_id > 0 ? server->getChannelTree()->findChannel(channel_id) : nullptr; - return this->server_channel; -} - ts_always_inline bool channel_ignore_permission(ts::permission::PermissionType type) { return permission::i_icon_id == type; } @@ -920,207 +914,8 @@ vector cache) { - if(permissions.empty()) return {}; - - vector> result; - result.reserve(permissions.size()); - - - if(!cache) { - cache = make_shared(); - } - - if(!cache->client_permissions) { - cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), client_dbid); - } - - bool have_skip_permission = false; - int skip_permission_type = -1; /* -1 := unset | 0 := skip, not explicit | 1 := skip, explicit */ - - /* - * server_group_data[0] := Server group id - * server_group_data[1] := Skip flag - * server_group_data[2] := Negate flag - * server_group_data[3] := Permission value - */ - typedef std::tuple GroupData; - bool server_group_data_initialized = false; - vector server_group_data; - GroupData* active_server_group; - - /* function to calculate skip permission */ - auto calculate_skip = [&]{ - skip_permission_type = 0; - /* test for skip permission within the client permission manager */ - { - auto skip_value = cache->client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions); - if(skip_value.has_value) { - have_skip_permission = skip_value.value == 1; - skip_permission_type = 1; - logTrace(this->serverId, "[Permission] Found skip permission in client permissions. Value: {}", have_skip_permission); - } - } - /* test for skip permission within all server groups */ - if(skip_permission_type != 1) { - for(const auto& assignment : cache->getGroupAssignments(this, client_dbid, client_type)) { - auto group_permissions = assignment->group->permissions(); - auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions); - if(flagged_value.has_value) { - have_skip_permission |= flagged_value.value == 1; - if(have_skip_permission) { - logTrace(this->serverId, "[Permission] Found skip permission in client server group. Group: {} ({}), Value: {}", assignment->group->groupId(), assignment->group->name(), have_skip_permission); - break; - } - } - } - } - }; - - auto initialize_group_data = [&](const permission::PermissionType& permission_type) { - server_group_data_initialized = true; - active_server_group = nullptr; - - auto assigned_groups = cache->getGroupAssignments(this, client_dbid, client_type); - server_group_data.resize(assigned_groups.size()); - auto it = server_group_data.begin(); - for(auto& group : assigned_groups) { - auto group_permissions = group->group->permissions(); - auto permission_flags = group_permissions->permission_flags(permission_type); - - auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set; - if(!flag_set) - continue; - - //TODO: Test if there is may a group channel permissions - auto value = group_permissions->permission_values(permission_type); - *it = std::make_tuple(group->group->groupId(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value); - it++; - } - if(it == server_group_data.begin()) - return; /* no server group has that permission */ - - server_group_data.erase(it, server_group_data.end()); /* remove unneeded */ - - auto found_negate = false; - for(auto& group : server_group_data) { - if(std::get<2>(group)) { - found_negate = true; - break; - } - } - - if(found_negate) { - server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end()); - logTrace(this->serverId, "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size()); - if(server_group_data.empty()) - logTrace(this->serverId, "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, client_dbid); - permission::PermissionValue current_lowest = 0; - for(auto& group : server_group_data) { - if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) { - current_lowest = std::get<3>(group); - active_server_group = &group; - } - } - } else { - permission::PermissionValue current_highest = 0; - for(auto& group : server_group_data) { - if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) { - current_highest = std::get<3>(group); - active_server_group = &group; - } - } - } - }; - - for(const auto& permission : permissions) { - server_group_data_initialized = false; /* reset all group data */ - auto client_permission_flags = cache->client_permissions->permission_flags(permission); - /* lets try to resolve the channel specific permission */ - if(channel_id > 0 && client_permission_flags.channel_specific) { - auto data = cache->client_permissions->channel_permission(permission, channel_id); - if(calculate_granted ? data.flags.grant_set : data.flags.value_set) { - result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}}); - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.values.value); - continue; - } - } - - - bool skip_channel_permissions = channel_id == 0; - if(!skip_channel_permissions) { - /* look if somewhere is the skip permission flag set */ - if(skip_permission_type == -1) {/* initialize skip flag */ - calculate_skip(); - } - skip_channel_permissions = have_skip_permission; - } - if(!skip_channel_permissions) { - /* okey we've no global skip. Then now lookup the groups and the client permissions */ - if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) { - /* okey the client has the permission, this counts */ - skip_channel_permissions = client_permission_flags.skip; - } else { - if(!server_group_data_initialized) - initialize_group_data(permission); - - if(active_server_group) - skip_channel_permissions = std::get<1>(*active_server_group); - } - } - - if(!skip_channel_permissions) { - /* lookup the channel group */ - { - auto channel_assignment = cache->getChannelAssignment(this, client_dbid, channel_id); - if(channel_assignment) { - auto group_permissions = channel_assignment->group->permissions(); - auto permission_flags = group_permissions->permission_flags(permission); - - auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set; - if(flag_set) { - auto value = group_permissions->permission_values(permission); - result.push_back({permission, {calculate_granted ? value.grant : value.value, true}}); - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", client_dbid, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value); - continue; - } - } - } - - /* lookup the channel permissions. Whyever? */ - { - auto channel = cache->getServerChannel(this, channel_id); - if(channel) { - auto channel_permissions = channel->permissions(); - auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission); - if(data.has_value) { - result.push_back({permission, {data.value, true}}); - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.value); - continue; - } - } - } - } - - if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) { - auto client_value = cache->client_permissions->permission_values(permission); - result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}}); - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", client_dbid, permission::resolvePermissionData(permission)->name, client_value.value); - continue; - } - - if(!server_group_data_initialized) - initialize_group_data(permission); - if(active_server_group) { - result.push_back({permission, {get<3>(*active_server_group), true}}); - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", client_dbid, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group)); - continue; - } - - logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned in no permission.", client_dbid, permission::resolvePermissionData(permission)->name); - result.push_back({permission, {permNotGranted, false}}); - } - - return result; + ClientPermissionCalculator calculator{this->ref(), client_dbid, client_type, channel_id}; + return calculator.calculate_permissions(permissions, calculate_granted); } permission::v2::PermissionFlaggedValue VirtualServer::calculate_permission( @@ -1137,13 +932,16 @@ permission::v2::PermissionFlaggedValue VirtualServer::calculate_permission( } bool VirtualServer::verifyServerPassword(std::string password, bool hashed) { - if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as_or(false)) return true; - if(password.empty()) return false; + if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as_or(false)) { + return true; + } - if(!hashed){ - char buffer[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(password.data()), password.length(), reinterpret_cast(buffer)); - password = base64_encode(string(buffer, SHA_DIGEST_LENGTH)); + if(password.empty()) { + return false; + } + + if(!hashed) { + password = base64::encode(digest::sha1(password)); } return password == this->properties()[property::VIRTUALSERVER_PASSWORD].value(); @@ -1175,105 +973,70 @@ VirtualServer::NetworkReport VirtualServer::generate_network_report() { } 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()); + std::map server_group_mapping{}; + std::map channel_group_mapping{}; + { + this->group_manager()->server_groups()->reset_groups(serverInstance->group_manager(), server_group_mapping); + this->group_manager()->channel_groups()->reset_groups(serverInstance->group_manager(), channel_group_mapping); + this->group_manager()->assignments().reset_all(); + } + /* assign the properties */ + { + this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = + server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as_or(0)]; + + this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP] = + server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or(0)]; + + this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = + channel_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as_or(0)]; + + this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = + channel_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as_or(0)]; + } { - threads::MutexLock lock(this->group_manager()->cacheLock); - this->group_manager()->deleteAllGroups(); - deque> saved_groups; - for(const auto& group : serverInstance->group_manager()->availableGroups(false)){ - if(group->type() != GroupType::GROUP_TYPE_TEMPLATE) continue; + this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; - debugMessage(this->serverId, "Copy default group {{Id: {}, Type: {}, Target: {}, Name: {}}} to server", group->groupId(), group->type(), group->target(), group->name()); - this->group_manager()->copyGroup(group, GroupType::GROUP_TYPE_NORMAL, group->name(), this->serverId); + auto admin_group_id = server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as_or(0)]; + auto admin_group = this->group_manager()->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, admin_group_id); + if(!admin_group) { + logCritical(this->getServerId(), "Missing default server admin group. Don't generate a new token."); + new_permission_token = "missing server admin group"; + } else { + 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."); + } else { + std::vector actions{}; + actions.push_back(token::TokenAction{ + .id = 0, + .type = token::ActionType::AddServerGroup, + .id1 = admin_group->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; + } } } - //Server admin - auto default_server_admin = serverInstance->group_manager()->findGroup( - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as_or(0)); - auto default_server_music = serverInstance->group_manager()->findGroup( - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or(0)); - auto default_server_guest = serverInstance->group_manager()->findGroup( - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as_or(0)); - - auto default_channel_admin = serverInstance->group_manager()->findGroup( - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as_or(0)); - auto default_channel_guest = serverInstance->group_manager()->findGroup( - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as_or(0)); - - if(!default_server_guest) { - logCritical(0, "Missing default server guest template group!"); - assert(!serverInstance->group_manager()->availableChannelGroups().empty()); - - default_server_guest = serverInstance->group_manager()->availableServerGroups().front(); - logCritical(0, "Using group {} as default server guest group for server {}.", default_server_guest->name(), this->serverId); - } - if(!default_channel_admin) { - logCritical(0, "Missing default channel guest template group!"); - assert(!serverInstance->group_manager()->availableChannelGroups().empty()); - - default_channel_admin = serverInstance->group_manager()->availableChannelGroups().front(); - logCritical(0, "Using group {} as channel server guest group for server {}.", default_channel_admin->name(), this->serverId); - } - if(!default_server_music) { - logCritical(0, "Missing default channel guest template group!"); - assert(!serverInstance->group_manager()->availableChannelGroups().empty()); - - default_server_music = serverInstance->group_manager()->availableChannelGroups().front(); - logCritical(0, "Using group {} as channel server guest group for server {}.", default_server_music->name(), this->serverId); - } - - if(!default_server_admin) { - logCritical(0, "Missing default server admin template group! Using default guest group ({})", default_server_guest->name()); - default_server_admin = default_server_guest; - } - - if(!default_channel_admin) { - logCritical(0, "Missing default channel admin template group! Using default guest group ({})", default_channel_guest->name()); - default_channel_admin = default_channel_guest; - } - - this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = this->group_manager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_guest->name()).front()->groupId(); - this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP] = this->group_manager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_music->name()).front()->groupId(); - this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = this->group_manager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_admin->name()).front()->groupId(); - this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->group_manager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId(); - - auto server_admin_group_id = this->group_manager()->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 { - std::vector 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; - } this->ensureValidDefaultGroups(); for(const auto& client : this->getClients()) { - if(client->getType() != ClientType::CLIENT_QUERY) { - client->notifyServerGroupList(); - client->notifyChannelGroupList(); - } - if(this->notifyClientPropertyUpdates(client, this->group_manager()->update_server_group_property(client, true, client->getChannel()))) { - client->task_update_needed_permissions.enqueue(); - } + client->task_update_displayed_groups.enqueue(); + client->task_update_needed_permissions.enqueue(); client->task_update_channel_client_properties.enqueue(); } + + this->task_notify_channel_group_list.enqueue(); + this->task_notify_server_group_list.enqueue(); + return true; } @@ -1395,15 +1158,16 @@ void VirtualServer::update_channel_from_permissions(const std::shared_ptrchannelId()); } } - if(!deleted.empty()) + if(!deleted.empty()) { cl->notifyChannelHide(deleted, false); + } } }); } } std::shared_ptr VirtualServer::default_channel_group() { - auto group_id = this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_save(); + auto group_id = this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or(0); auto group = this->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { auto groups = this->group_manager()->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 7e3e5b1..d48c882 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -110,25 +110,7 @@ namespace ts { uint16_t bots = 0; }; - struct CalculateCache { - bool global_skip = false; - bool global_skip_set = false; - - std::shared_ptr client_permissions; - std::vector> assignment_server_groups; - bool assignment_server_groups_set = false; - - ChannelId assignment_channel_group_channel; - std::shared_ptr assignment_channel_group; - bool assignment_channel_group_set = false; - - std::shared_ptr server_channel; - ChannelId last_server_channel = 0; - - inline std::vector> getGroupAssignments(VirtualServer* server, ClientDbId cldbid, ClientType type); - inline std::shared_ptr getChannelAssignment(VirtualServer* server, ClientDbId client_dbid, ChannelId channel); - inline std::shared_ptr getServerChannel(VirtualServer*, ChannelId); - }; + struct CalculateCache {}; class VirtualServer { friend class WebClient; @@ -258,8 +240,6 @@ namespace ts { ServerState::value getState() { return this->state; } - bool could_default_create_channel(); - inline std::shared_ptr ref() { return this->self.lock(); } inline bool disable_ip_saving() { return this->_disable_ip_saving; } inline std::chrono::system_clock::time_point start_timestamp() { return this->startTimestamp; }; @@ -299,6 +279,9 @@ namespace ts { inline auto& get_channel_tree_lock() { return this->channel_tree_lock; } void update_channel_from_permissions(const std::shared_ptr& /* channel */, const std::shared_ptr& /* issuer */); + + inline void enqueue_notify_channel_group_list() { this->task_notify_channel_group_list.enqueue(); } + inline void enqueue_notify_server_group_list() { this->task_notify_server_group_list.enqueue(); } protected: bool registerClient(std::shared_ptr); bool unregisterClient(std::shared_ptr, std::string, std::unique_lock& channel_tree_lock); @@ -363,6 +346,9 @@ namespace ts { std::chrono::milliseconds spoken_time{0}; std::chrono::system_clock::time_point spoken_time_timestamp; + multi_shot_task task_notify_channel_group_list{}; + multi_shot_task task_notify_server_group_list{}; + bool _disable_ip_saving = false; }; } diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 2f7e034..63b01b6 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -57,6 +57,13 @@ void ConnectedClient::initialize_weak_reference(const std::shared_ptrupdateChannelClientProperties(true, true); } }}; + + this->task_update_displayed_groups = multi_shot_task{serverInstance->general_task_executor(), "update displayed groups for " + this->getLoggingPeerIp(), [weak_self]{ + auto self = weak_self.lock(); + if(self) { + self->update_displayed_client_groups(); + } + }}; } bool ConnectedClient::loadDataForCurrentServer() { @@ -1012,6 +1019,63 @@ bool ConnectedClient::update_client_needed_permissions() { return updated; } +void ConnectedClient::update_displayed_client_groups(bool& server_groups_changed, bool& channel_group_changed) { + auto ref_server = this->server; + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); + + GroupId channel_group_id{0}; + ChannelId channel_inherit_id{0}; + std::string server_group_assignments{}; + + { + auto server_groups = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId()); + for(const auto& group_id : server_groups) { + server_group_assignments += ","; + server_group_assignments += std::to_string(group_id); + } + + if(!server_group_assignments.empty()) { + server_group_assignments = server_group_assignments.substr(1); + } + } + + { + std::shared_ptr inherited_channel{this->currentChannel}; + auto channel_group = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), inherited_channel); + if(channel_group.has_value()) { + assert(inherited_channel); + channel_group_id = *channel_group; + channel_inherit_id = inherited_channel->channelId(); + } else { + channel_group_id = 0; + channel_inherit_id = 0; + } + } + + server_groups_changed = false; + channel_group_changed = false; + + std::deque updated_properties{}; + if(this->properties()[property::CLIENT_SERVERGROUPS].update_value(server_group_assignments)) { + updated_properties.push_back(property::CLIENT_SERVERGROUPS); + server_groups_changed = true; + } + + if(this->properties()[property::CLIENT_CHANNEL_GROUP_ID].update_value(channel_group_id)) { + updated_properties.push_back(property::CLIENT_CHANNEL_GROUP_ID); + channel_group_changed = true; + } + + if(this->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].update_value(channel_inherit_id)) { + updated_properties.push_back(property::CLIENT_CHANNEL_GROUP_ID); + channel_group_changed = true; + } + + if(!updated_properties.empty() && ref_server) { + ref_server->notifyClientPropertyUpdates(this->ref(), updated_properties); + } +} + void ConnectedClient::sendTSPermEditorWarning() { if(config::voice::warn_on_permission_editor) { if(system_clock::now() - this->command_times.servergrouplist > milliseconds(1000)) return; @@ -1192,11 +1256,7 @@ void ConnectedClient::useToken(token::TokenId token_id) { } if(tree_registered && (server_groups_changed || channel_group_changed)) { - /* TODO: Set "Update server groups" as task */ - auto updated_properties = this->getServer()->group_manager()->update_server_group_property(this->ref(), true, this->currentChannel); - if(!updated_properties.empty()) { - this->getServer()->notifyClientPropertyUpdates(this->ref(), updated_properties); - } + this->task_update_displayed_groups.enqueue(); } if(tree_registered && server_groups_changed) { diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index ee5506b..65472a7 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -57,6 +57,12 @@ namespace ts { class WebClient; class MusicClient; + namespace groups { + class Group; + class ServerGroup; + class ChannelGroup; + } + struct ConnectionInfoData { std::chrono::time_point timestamp; std::map properties; @@ -74,7 +80,6 @@ namespace ts { friend class DataClient; friend class SpeakingClient; friend class connection::VoiceClientConnection; - friend class ts::GroupManager; friend class VirtualServerManager; public: explicit ConnectedClient(sql::SqlManager*, const std::shared_ptr& server); @@ -129,14 +134,15 @@ namespace ts { /** Notifies (after request) */ bool sendNeededPermissions(bool /* force an update */); /* invoke this because it dosn't spam the client */ virtual bool notifyClientNeededPermissions(); - virtual bool notifyServerGroupList(bool as_notify = true); - virtual bool notifyGroupPermList(const std::shared_ptr&, bool); + virtual bool notifyGroupPermList(const std::shared_ptr&, bool); virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr&, bool); - virtual bool notifyChannelGroupList(bool as_notify = true); virtual bool notifyConnectionInfo(const std::shared_ptr &target, const std::shared_ptr &info); virtual bool notifyChannelSubscribed(const std::deque> &); virtual bool notifyChannelUnsubscribed(const std::deque> &); + virtual bool notifyServerGroupList(std::optional& /* generated notify */, bool /* as notify */); + virtual bool notifyChannelGroupList(std::optional& /* generated notify */, bool /* as notify */); + /** Notifies (without request) */ //Group server virtual bool notifyServerUpdated(std::shared_ptr); @@ -299,6 +305,12 @@ namespace ts { */ bool update_client_needed_permissions(); + /** + * Attention: This method should never be called directly! + * Use `task_update_displayed_groups` instead to schedule an update. + */ + void update_displayed_client_groups(bool& server_groups_changed, bool& channel_group_changed); + std::shared_lock require_connected_state(bool blocking = false) { //try_to_lock_t std::shared_lock disconnect_lock{}; @@ -398,6 +410,7 @@ namespace ts { multi_shot_task task_update_needed_permissions{}; multi_shot_task task_update_channel_client_properties{}; + multi_shot_task task_update_displayed_groups{}; bool loadDataForCurrentServer() override; diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index 3aac6ba..da2d8b0 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -9,6 +9,7 @@ #include #include #include "./web/WebClient.h" +#include "../groups/GroupManager.h" using namespace std::chrono; using namespace std; @@ -41,41 +42,83 @@ do { \ } \ } while(0) -bool ConnectedClient::notifyServerGroupList(bool as_notify) { - Command cmd(as_notify ? "notifyservergrouplist" : ""); - int index = 0; +template +inline void build_group_notify(ts::command_builder& notify, bool is_channel_groups, const std::vector>& available_groups) { + std::string group_id_key{}; + permission::PermissionType permission_modify{}; + permission::PermissionType permission_add{}; + permission::PermissionType permission_remove{}; - for (const auto& group : (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->availableServerGroups(true)) { - if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { - cmd[index]["cgid"] = group->groupId(); - } else { - cmd[index]["sgid"] = group->groupId(); - } - for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) - cmd[index][std::string{prop.type().name}] = prop.value(); + if(is_channel_groups) { + group_id_key = "cgid"; + permission_modify = permission::i_channel_group_needed_modify_power; + permission_add = permission::i_channel_group_needed_member_add_power; + permission_remove = permission::i_channel_group_needed_member_remove_power; + } else { + group_id_key = "sgid"; - auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); - auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); - auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); - cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; - cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; - cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; - index++; + permission_modify = permission::i_server_group_needed_modify_power; + permission_add = permission::i_server_group_needed_member_add_power; + permission_remove = permission::i_server_group_needed_member_remove_power; } - this->sendCommand(cmd); + size_t index{0}; + for(const auto& group : available_groups) { + auto bulk = notify.bulk(index++); + bulk.put_unchecked(group_id_key, group->group_id()); + bulk.put_unchecked("type", (uint8_t) group->group_type()); + bulk.put_unchecked("name", group->display_name()); + bulk.put_unchecked("sortid", group->sort_id()); + bulk.put_unchecked("savedb", group->save_assignments()); + bulk.put_unchecked("namemode", (uint8_t) group->name_mode()); + bulk.put_unchecked("iconid", group->icon_id()); + + auto modify_power = group->permissions()->permission_value_flagged(permission_modify); + auto add_power = group->permissions()->permission_value_flagged(permission_add); + auto remove_power = group->permissions()->permission_value_flagged(permission_remove); + + bulk.put_unchecked("n_modifyp", modify_power.has_value ? modify_power.value : 0); + bulk.put_unchecked("n_member_addp", add_power.has_value ? add_power.value : 0); + bulk.put_unchecked("n_member_removep", remove_power.has_value ? remove_power.value : 0); + } +} + +bool ConnectedClient::notifyServerGroupList(std::optional &generated_notify, bool as_notify) { + if(!generated_notify.has_value()) { + auto server_ref = this->server; + auto group_manager = server_ref ? server_ref->group_manager() : serverInstance->group_manager(); + auto available_groups = group_manager->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); + + build_group_notify(generated_notify.emplace(as_notify ? "notifyservergrouplist" : ""), false, available_groups); + } + + this->sendCommand(*generated_notify); return true; } +bool ConnectedClient::notifyChannelGroupList(std::optional& generated_notify, bool as_notify) { + if(!generated_notify.has_value()) { + auto server_ref = this->server; + auto group_manager = server_ref ? server_ref->group_manager() : serverInstance->group_manager(); + auto available_groups = group_manager->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); -bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, bool as_sid) { - ts::command_builder result{this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""}; + build_group_notify(generated_notify.emplace(as_notify ? "notifychannelgrouplist" : ""), true, available_groups); + } - if (group->target() == GROUPTARGET_SERVER) - result.put_unchecked(0, "sgid", group->groupId()); - else - result.put_unchecked(0, "cgid", group->groupId()); + this->sendCommand(*generated_notify); + return true; +} + +bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, bool as_sid) { + auto is_channel_group = !!dynamic_pointer_cast(group); + ts::command_builder result{this->notify_response_command(is_channel_group ? "notifychannelgrouppermlist" : "notifyservergrouppermlist")}; + + if (!is_channel_group) { + result.put_unchecked(0, "sgid", group->group_id()); + } else { + result.put_unchecked(0, "cgid", group->group_id()); + } int index = 0; @@ -168,32 +211,6 @@ bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ return true; } -bool ConnectedClient::notifyChannelGroupList(bool as_notify) { - Command cmd(as_notify ? "notifychannelgrouplist" : ""); - int index = 0; - for (const auto &group : (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->availableChannelGroups(true)) { - if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { - cmd[index]["cgid"] = group->groupId(); - } else { - cmd[index]["sgid"] = group->groupId(); - } - for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) - cmd[index][std::string{prop.type().name}] = prop.value(); - - - auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); - auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); - auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); - cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; - cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; - cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; - index++; - } - if(index == 0) return false; - this->sendCommand(cmd); - return true; -} - bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, uint64_t targetId, ChannelId channel_id, const std::chrono::system_clock::time_point& timestamp, const string &textMessage) { //notifytextmessage targetmode=1 msg=asdasd target=2 invokerid=1 invokername=WolverinDEV invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= Command cmd("notifytextmessage"); diff --git a/server/src/client/DataClient.cpp b/server/src/client/DataClient.cpp index 45e485b..f61e0a6 100644 --- a/server/src/client/DataClient.cpp +++ b/server/src/client/DataClient.cpp @@ -6,6 +6,7 @@ #include "./DataClient.h" #include "../InstanceHandler.h" #include "../groups/GroupManager.h" +#include "../groups/GroupAssignmentManager.h" using namespace std; using namespace ts; @@ -196,22 +197,32 @@ std::vector> DataClient::assignedServerGrou } } + if(result.empty() && ref_server) { + auto default_group_id = ref_server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as_or(0); + auto default_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, default_group_id); + if(default_group) { + result.push_back(default_group); + } + } + return result; } -std::shared_ptr DataClient::assignedChannelGroup(const shared_ptr &channel) { +std::shared_ptr DataClient::assignedChannelGroup(const std::shared_ptr &channel) { auto ref_server = this->server; assert(channel); if(!channel || !ref_server) { return nullptr; } - std::shared_ptr result{}; + std::shared_ptr inherited_channel{channel}; auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); - auto channel_group_assignment = group_manager->assignments().channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), channel->channelId()); - if(channel_group_assignment.has_value()) { - result = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, channel_group_assignment->group_id); + auto assigned_group_id = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), inherited_channel); + + std::shared_ptr result{}; + if(assigned_group_id.has_value()) { + result = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, *assigned_group_id); } if(!result) { diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index ae5b156..57b97ce 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -517,8 +517,14 @@ void SpeakingClient::processJoin() { TIMING_STEP(timings, "join move "); this->properties()->triggerAllModified(); - this->notifyServerGroupList(); - this->notifyChannelGroupList(); + { + std::optional generated_notify{}; + this->notifyServerGroupList(generated_notify, true); + } + { + std::optional generated_notify{}; + this->notifyChannelGroupList(generated_notify, true); + } TIMING_STEP(timings, "notify grou"); logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} joined.", this->getClientDatabaseId(), diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index 518415c..a63ae0f 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -7,6 +7,7 @@ #include "../../manager/PermissionNameMapper.h" #include "../../server/QueryServer.h" #include "../../server/VoiceServer.h" +#include "../../groups/GroupManager.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../music/MusicClient.h" @@ -137,58 +138,63 @@ command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); log::GroupType log_group_type; - if (cmd["type"].as() == 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::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!"}; - } + auto group_type = cmd["type"].as(); + switch (group_type) { + case groups::GroupType::GROUP_TYPE_QUERY: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; + break; - log_group_type = log::GroupType::NORMAL; + case groups::GroupType::GROUP_TYPE_TEMPLATE: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + break; + + case groups::GroupType::GROUP_TYPE_NORMAL: + if (!this->server) { + return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"}; + } + log_group_type = log::GroupType::NORMAL; + break; + + case groups::GroupType::GROUP_TYPE_UNKNOWN: + default: + return command_result{error::parameter_invalid, "type"}; } - if (cmd["name"].string().empty()) { - return command_result{error::parameter_invalid, "invalid group name"}; - } + std::shared_ptr group{}; + auto result = group_manager->channel_groups()->create_group(group_type, cmd["name"].string(), group); + switch(result) { + case groups::GroupCreateResult::SUCCESS: + break; - 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"}; - } - } + case groups::GroupCreateResult::NAME_TOO_SHORT: + case groups::GroupCreateResult::NAME_TOO_LONG: + return command_result{error::parameter_invalid, "name"}; - auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as(), 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, ""); + case groups::GroupCreateResult::NAME_ALREADY_IN_USED: + return command_result{error::group_name_inuse}; + + case groups::GroupCreateResult::DATABASE_ERROR: + default: + return command_result{error::vs_critical}; + } + assert(group); + serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, group->group_id(), group->display_name(), 0, ""); { ts::command_builder notify{this->notify_response_command("notifychannelgroupadded")}; - notify.put_unchecked(0, "cgid", group->groupId()); + notify.put_unchecked(0, "cgid", group->group_id()); this->sendCommand(notify); } if (group) { group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing); if (this->server) { - if (this->getType() == ClientType::CLIENT_QUERY) { - this->server->forEachClient([&](const std::shared_ptr &cl) { - if (cl == this) { - return; - } - - cl->notifyChannelGroupList(); - }); - } else { - this->server->forEachClient([](const std::shared_ptr &cl) { - cl->notifyChannelGroupList(); - }); - } + this->server->enqueue_notify_channel_group_list(); } } else { return command_result{error::group_invalid_id}; @@ -200,30 +206,40 @@ command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); auto ref_server = this->server; - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); auto source_group_id = cmd["scgid"].as(); - auto source_group = group_manager->findGroup(source_group_id); + auto source_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, source_group_id); - if (!source_group || source_group->target() != GROUPTARGET_CHANNEL) - return command_result{error::group_invalid_id, "invalid source group"}; + if (!source_group) { + return command_result{error::group_invalid_id, "invalid source group id"}; + } - const auto group_type_modificable = [&](GroupType type) { + const auto group_type_modifiable = [&](groups::GroupType type) { switch (type) { - case GroupType::GROUP_TYPE_TEMPLATE: - if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) + case groups::GroupType::GROUP_TYPE_TEMPLATE: + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) { return permission::b_serverinstance_modify_templates; + } + break; - case GroupType::GROUP_TYPE_QUERY: - if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) + case groups::GroupType::GROUP_TYPE_QUERY: + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) { return permission::b_serverinstance_modify_querygroup; + } + break; - case GroupType::GROUP_TYPE_NORMAL: + case groups::GroupType::GROUP_TYPE_NORMAL: + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_channelgroup_create, 0))) { + return permission::b_virtualserver_channelgroup_create; + } + break; + + case groups::GroupType::GROUP_TYPE_UNKNOWN: default: break; } @@ -231,107 +247,149 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { }; { - auto result = group_type_modificable(source_group->type()); - if (result != permission::undefined) + auto result = group_type_modifiable(source_group->group_type()); + if (result != permission::undefined) { return command_result{result}; + } } auto global_update = false; if (cmd[0].has("tcgid") && cmd["tcgid"].as() != 0) { //Copy an existing group - auto target_group = group_manager->findGroup(cmd["tcgid"]); - if (!target_group || target_group->target() != GROUPTARGET_CHANNEL) + auto target_group_id = cmd["tcgid"].as(); + auto target_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, target_group_id); + if (!target_group) { return command_result{error::group_invalid_id, "invalid target group"}; - - { - auto result = group_type_modificable(target_group->type()); - if (result != permission::undefined) - return command_result{result}; } - if (!target_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission(permission::i_channel_group_modify_power, 0), true)) - return command_result{permission::i_channel_group_modify_power}; + if(!target_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission(permission::i_channel_group_modify_power, 0), true)) { + return ts::command_result{permission::i_channel_group_needed_modify_power}; + } - if (!group_manager->copyGroupPermissions(source_group, target_group)) - return command_result{error::vs_critical, "failed to copy group permissions"}; + { + auto result = group_type_modifiable(target_group->group_type()); + if (result != permission::undefined) { + return command_result{result}; + } + } + + auto result = group_manager->channel_groups()->copy_group_permissions(source_group_id, target_group_id); + switch(result) { + case groups::GroupCopyResult::SUCCESS: + break; + + case groups::GroupCopyResult::UNKNOWN_SOURCE_GROUP: + return command_result{error::vs_critical, "internal unknown source group"}; + + case groups::GroupCopyResult::UNKNOWN_TARGET_GROUP: + return command_result{error::vs_critical, "internal unknown target group"}; + + case groups::GroupCopyResult::DATABASE_ERROR: + return command_result{error::vs_critical, "database error"}; + + case groups::GroupCopyResult::NAME_ALREADY_IN_USE: + return command_result{error::group_name_inuse}; + + default: + return command_result{error::vs_critical}; + } log::GroupType log_group_type; - switch (target_group->type()) { - case GroupType::GROUP_TYPE_QUERY: + switch (target_group->group_type()) { + case groups::GroupType::GROUP_TYPE_QUERY: log_group_type = log::GroupType::QUERY; break; - case GroupType::GROUP_TYPE_TEMPLATE: + case groups::GroupType::GROUP_TYPE_TEMPLATE: log_group_type = log::GroupType::TEMPLATE; break; - case GroupType::GROUP_TYPE_NORMAL: + case groups::GroupType::GROUP_TYPE_NORMAL: + case groups::GroupType::GROUP_TYPE_UNKNOWN: + default: 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()); + serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->group_type() != groups::GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group->group_id(), target_group->display_name(), source_group->group_id(), source_group->display_name()); - global_update = !this->server || !group_manager->isLocalGroup(target_group); + global_update = !this->server || !group_manager->channel_groups()->find_group(groups::GroupCalculateMode::LOCAL, target_group_id); } else { //Copy a new group - auto target_type = cmd["type"].as(); - - { - auto result = group_type_modificable(target_type); - if (result != permission::undefined) - return command_result{result}; - } + auto target_type = cmd["type"].as(); log::GroupType log_group_type; switch (target_type) { - case GroupType::GROUP_TYPE_QUERY: + case groups::GroupType::GROUP_TYPE_QUERY: log_group_type = log::GroupType::QUERY; break; - case GroupType::GROUP_TYPE_TEMPLATE: + case groups::GroupType::GROUP_TYPE_TEMPLATE: log_group_type = log::GroupType::TEMPLATE; break; - case GroupType::GROUP_TYPE_NORMAL: + case groups::GroupType::GROUP_TYPE_NORMAL: log_group_type = log::GroupType::NORMAL; break; + case groups::GroupType::GROUP_TYPE_UNKNOWN: default: return command_result{error::parameter_invalid, "type"}; } - if (!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) + { + auto result = group_type_modifiable(target_type); + if (result != permission::undefined) { + return command_result{result}; + } + } + + if (!ref_server && target_type == groups::GroupType::GROUP_TYPE_NORMAL) { return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; + } - if (!group_manager->findGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["name"].string()).empty()) - return command_result{error::group_name_inuse, "You cant create normal groups on the template server!"}; + std::shared_ptr created_group{}; + auto result = group_manager->channel_groups()->copy_group(source_group_id, target_type, cmd["name"].string(), created_group); + switch(result) { + case groups::GroupCopyResult::SUCCESS: + break; - 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()); + case groups::GroupCopyResult::UNKNOWN_SOURCE_GROUP: + return command_result{error::vs_critical, "internal unknown source group"}; - if (this->getType() == ClientType::CLIENT_QUERY) { - Command notify(""); - notify["cgid"] = target_group_id; + case groups::GroupCopyResult::UNKNOWN_TARGET_GROUP: + return command_result{error::vs_critical, "internal unknown target group"}; + + case groups::GroupCopyResult::DATABASE_ERROR: + return command_result{error::vs_critical, "database error"}; + + case groups::GroupCopyResult::NAME_ALREADY_IN_USE: + return command_result{error::group_name_inuse}; + + default: + return command_result{error::vs_critical}; + } + + assert(created_group); + serverInstance->action_logger()->group_logger.log_group_create(target_type != groups::GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, log_group_type, created_group->group_id(), cmd["name"], source_group->group_id(), source_group->display_name()); + + { + ts::command_builder notify{this->notify_response_command("notifychannelgroupcopied")}; + notify.put_unchecked(0, "cgid", created_group->group_id()); this->sendCommand(notify); } - global_update = !this->server || !group_manager->isLocalGroup(group_manager->findGroup(target_group_id)); + global_update = !this->server; } - for (const auto &server : (global_update ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) - if (server) - server->forEachClient([](shared_ptr cl) { - cl->notifyChannelGroupList(); - }); + for (const auto &server : (global_update ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) { + if (server) { + server->enqueue_notify_channel_group_list(); + } + } return command_result{error::ok}; } @@ -339,32 +397,50 @@ command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - auto channel_group = group_manager->findGroup(cmd["cgid"].as()); - if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) - return command_result{error::parameter_invalid, "invalid channel group id"}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto channel_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if (!channel_group) { + return command_result{error::group_invalid_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(); + auto type = channel_group->group_type(); log::GroupType log_group_type; - if (type == GroupType::GROUP_TYPE_QUERY) { + if (type == groups::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) { + } else if (type == groups::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); + auto old_name = channel_group->display_name(); + auto result = group_manager->channel_groups()->rename_group(channel_group->group_id(), cmd["name"].string()); + switch(result) { + case groups::GroupRenameResult::SUCCESS: + break; - if (this->server) - this->server->forEachClient([](shared_ptr cl) { - cl->notifyChannelGroupList(); - }); + case groups::GroupRenameResult::INVALID_GROUP_ID: + return ts::command_result{error::vs_critical, "internal invalid group id"}; + + case groups::GroupRenameResult::NAME_INVALID: + return ts::command_result{error::parameter_invalid, "name"}; + + case groups::GroupRenameResult::NAME_ALREADY_USED: + return ts::command_result{error::group_name_inuse}; + + case groups::GroupRenameResult::DATABASE_ERROR: + default: + return ts::command_result{error::vs_critical}; + } + + serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, channel_group->group_id(), channel_group->display_name(), old_name); + if (this->server) { + this->server->enqueue_notify_channel_group_list(); + } return command_result{error::ok}; } @@ -372,54 +448,101 @@ command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_delete, 1); - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - auto channel_group = group_manager->findGroup(cmd["cgid"].as()); - if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + auto ref_server = this->server; + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); + auto channel_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if (!channel_group) { + return command_result{error::group_invalid_id}; + } + + if(!channel_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission(permission::i_channel_group_modify_power, 0), true)) { + return command_result{permission::i_channel_group_needed_modify_power}; + } if (this->server) { - if (this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] == channel_group->groupId()) + if (this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] == channel_group->group_id()) { return command_result{error::parameter_invalid, "Could not delete default channel group!"}; - if (this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] == channel_group->groupId()) + } + if (this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] == channel_group->group_id()) { return command_result{error::parameter_invalid, "Could not delete default channel admin group!"}; - } - 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()) - if (!group_manager->listGroupMembers(channel_group, false).empty()) - return command_result{error::database_empty_result, "group not empty!"}; - - 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 cl) { - if (this->server->notifyClientPropertyUpdates(cl, this->server->group_manager()->update_server_group_property(cl, true, cl->getChannel()))) { - cl->task_update_needed_permissions.enqueue(); - } - cl->notifyChannelGroupList(); - }); } } - if(this->server) { - this->server->tokenManager->handle_channel_group_deleted(channel_group->groupId()); + if (serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default channel group!"}; + } + + if (serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"}; + } + + log::GroupType log_group_type; + switch (channel_group->group_type()) { + case groups::GroupType::GROUP_TYPE_QUERY: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; + break; + + case groups::GroupType::GROUP_TYPE_TEMPLATE: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + break; + + case groups::GroupType::GROUP_TYPE_NORMAL: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_delete, 1); + log_group_type = log::GroupType::NORMAL; + break; + + case groups::GroupType::GROUP_TYPE_UNKNOWN: + default: + return ts::command_result{error::vs_critical}; + } + + if (!cmd["force"].as()) { + if(!group_manager->assignments().is_channel_group_empty(channel_group->group_id())) { + return command_result{error::group_not_empty}; + } + } + + auto result = group_manager->channel_groups()->delete_group(channel_group->group_id()); + switch(result) { + case groups::GroupDeleteResult::SUCCESS: + break; + + case groups::GroupDeleteResult::INVALID_GROUP_ID: + case groups::GroupDeleteResult::DATABASE_ERROR: + default: + return ts::command_result{error::vs_critical}; + } + + serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, channel_group->group_id(), channel_group->display_name()); + if(ref_server) { + auto channel_group_id = channel_group->group_id(); + + std::deque> affected_clients{}; + this->server->forEachClient([&](std::shared_ptr client) { + /* TODO: It might be faster to query all clients of the group and search within that list only */ + auto assigned_group = group_manager->assignments().exact_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, client->getClientDatabaseId(), client->getChannelId()); + if(assigned_group.has_value() && assigned_group->group_id == channel_group_id) { + affected_clients.push_back(client); + } + }); + + ref_server->enqueue_notify_channel_group_list(); + ref_server->group_manager()->assignments().handle_channel_group_deleted(channel_group_id); + + for(const auto& client : affected_clients) { + /* + * Now we can enqueue all the updates since handle_channel_group_deleted has already been called. + * If not done in that order, the group might already got updated before we actiually deleted it from the group manager. + */ + client->task_update_displayed_groups.enqueue(); + client->task_update_needed_permissions.enqueue(); + client->task_update_channel_client_properties.enqueue(); + } + + ref_server->tokenManager->handle_channel_group_deleted(channel_group_id); } return command_result{error::ok}; @@ -430,7 +553,9 @@ command_result ConnectedClient::handleCommandChannelGroupList(Command &) { CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_list, 1); - this->notifyChannelGroupList(this->getType() != ClientType::CLIENT_QUERY); + std::optional notify{}; + this->notifyChannelGroupList(notify, this->getType() != ClientType::CLIENT_QUERY); + this->command_times.servergrouplist = system_clock::now(); return command_result{error::ok}; } @@ -439,69 +564,29 @@ command_result ConnectedClient::handleCommandChannelGroupClientList(Command &cmd CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); + auto target_channel_id = cmd[0].has("cid") ? cmd["cid"].as() : 0; - if (target_channel_id > 0) { - ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1, target_channel_id); - } else { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1); - } - Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifychannelgroupclientlist" : ""); + auto target_client_database_id = cmd[0].has("cldbid") ? cmd["cldbid"].as() : 0; + auto target_group_id = cmd[0].has("cgid") ? cmd["cgid"].as() : 0; - deque variables{variable{":sid", this->getServerId()}}; - string query = "SELECT `groupId`, `cldbid`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid"; + ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1, target_channel_id); - if (cmd[0].has("cgid") && cmd["cgid"].as() > 0) { - auto group = this->server->group_manager()->findGroup(cmd["cgid"]); - if (!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) - return command_result{error::parameter_invalid, "invalid channel group id"}; - query += " AND `groupId` = :groupId"; - variables.push_back({":groupId", cmd["cgid"].as()}); - } else { - query += " AND `groupId` IN (SELECT `groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target)"; - variables.push_back({":target", GroupTarget::GROUPTARGET_CHANNEL}); + auto result = this->server->group_manager()->assignments().channel_group_list(target_group_id, target_channel_id, target_client_database_id); + if(result.empty()) { + return ts::command_result{error::database_empty_result}; } - if (cmd[0].has("cldbid") && cmd["cldbid"].as() > 0) { - query += " AND `cldbid` = :cldbid"; - variables.push_back({":cldbid", cmd["cldbid"].as()}); - } - if (cmd[0].has("cid") && cmd["cid"].as() > 0) { - auto channel = this->server->getChannelTree()->findChannel(cmd["cid"]); - if (!channel) - return command_result{error::parameter_invalid, "invalid channel id"}; - query += " AND `channelId` = :cid"; - variables.push_back({":cid", cmd["cid"].as()}); - } - debugMessage(this->getServerId(), "Command channelgroupclientlist sql: {}", query); - auto command = sql::command(this->sql, query); - for (const auto &variable : variables) - command.value(variable); - int index = 0; - command.query([&](Command &command, int &index, int length, string *values, string *names) { - GroupId group_id = 0; - ChannelId channel_id = 0; - ClientDbId cldbid = 0; - for (int i = 0; i < length; i++) { - try { - if (names[i] == "groupId") - group_id = stoll(values[i]); - else if (names[i] == "cldbid") - cldbid = stoll(values[i]); - else if (names[i] == "channelId") - channel_id = stoll(values[i]); - } catch (std::exception &ex) { - logError(this->getServerId(), "Failed to parse db field {}", names[i]); - } - } - result[index]["cid"] = channel_id; - result[index]["cgid"] = group_id; - result[index]["cldbid"] = cldbid; - index++; - }, - result, index); - if (index == 0) return command_result{error::database_empty_result}; - this->sendCommand(result); + size_t index{0}; + ts::command_builder notify{this->notify_response_command("notifychannelgroupclientlist"), 64, result.size()}; + for(const auto& entry : result) { + auto bulk = notify.bulk(index++); + bulk.put_unchecked("cgid", std::get<0>(entry)); + bulk.put_unchecked("cid", std::get<1>(entry)); + bulk.put_unchecked("cldbid", std::get<2>(entry)); + } + this->sendCommand(notify); + return command_result{error::ok}; } @@ -509,9 +594,15 @@ command_result ConnectedClient::handleCommandChannelGroupPermList(Command &cmd) CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_permission_list, 1); - auto channelGroup = (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->findGroup(cmd["cgid"].as()); - if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; - if (!this->notifyGroupPermList(channelGroup, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto channelGroup = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if (!channelGroup) { + return command_result{error::group_invalid_id}; + } + + if (!this->notifyGroupPermList(channelGroup, cmd.hasParm("permsid"))) { + return command_result{error::database_empty_result}; + } if (this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { this->sendTSPermEditorWarning(); @@ -523,9 +614,11 @@ command_result ConnectedClient::handleCommandChannelGroupPermList(Command &cmd) command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); - if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto channelGroup = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if (!channelGroup) { + return command_result{error::group_invalid_id}; + } ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; @@ -542,20 +635,14 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { log::PermissionTarget::CHANNEL_GROUP, permission::v2::PermissionUpdateType::set_value, 0, "", - channelGroup->groupId(), channelGroup->name()); + channelGroup->group_id(), channelGroup->display_name()); updateList |= ppermission.is_group_property(); } - if (updateList) { - channelGroup->apply_properties_from_permissions(); - } - if (this->server) { if (updateList) { - this->server->forEachClient([](shared_ptr cl) { - cl->notifyChannelGroupList(); - }); + this->server->enqueue_notify_channel_group_list(); } this->server->forEachClient([channelGroup](shared_ptr cl) { @@ -575,9 +662,12 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); - if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto channelGroup = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if (!channelGroup) { + return command_result{error::group_invalid_id}; + } ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; @@ -593,18 +683,13 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { log::PermissionTarget::CHANNEL_GROUP, permission::v2::PermissionUpdateType::delete_value, 0, "", - channelGroup->groupId(), channelGroup->name()); + channelGroup->group_id(), channelGroup->display_name()); updateList |= ppermission.is_group_property(); } - if (updateList) - channelGroup->apply_properties_from_permissions(); - if (this->server) { if (updateList) - this->server->forEachClient([](shared_ptr cl) { - cl->notifyChannelGroupList(); - }); + this->server->enqueue_notify_channel_group_list(); this->server->forEachClient([channelGroup](shared_ptr cl) { unique_lock client_channel_lock(cl->channel_lock); /* while we're updating groups we dont want to change anything! */ if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { @@ -632,7 +717,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { #define test_permission(required, permission_type) \ do { \ - if (!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false))) \ + if (!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false))) \ return command_result{permission_type}; \ } while (0) @@ -842,22 +927,25 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, parent_channel_id, false); if (max_channels.has_value) { - if (!permission::v2::permission_granted(created_perm + 1, max_channels)) + if (!permission::v2::permission_granted(created_perm + 1, max_channels)) { return command_result{permission::i_client_max_permanent_channels}; + } } } else if (cmd[0]["channel_flag_semi_permanent"].as()) { max_channels = this->calculate_permission(permission::i_client_max_semi_channels, parent_channel_id, false); if (max_channels.has_value) { - if (!permission::v2::permission_granted(created_semi + 1, max_channels)) + if (!permission::v2::permission_granted(created_semi + 1, max_channels)) { return command_result{permission::i_client_max_semi_channels}; + } } } else { max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, parent_channel_id, false); if (max_channels.has_value) { - if (!permission::v2::permission_granted(created_tmp + 1, max_channels)) + if (!permission::v2::permission_granted(created_tmp + 1, max_channels)) { return command_result{permission::i_client_max_temporary_channels}; + } } } } @@ -873,7 +961,9 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { channel_deep++; { const auto typed_parent = dynamic_pointer_cast(local_parent->entry); - if (typed_parent->deleted) return command_result{error::channel_is_deleted, "One of the parents has been deleted"}; + if (typed_parent->deleted) { + return command_result{error::channel_is_deleted, "One of the parents has been deleted"}; + } } local_parent = local_parent->parent.lock(); } @@ -955,15 +1045,23 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { if (this->server) { const auto self_lock = this->ref(); - GroupId adminGroup = this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP]; - auto channel_admin_group = this->server->group_manager()->findGroup(adminGroup); - if (!channel_admin_group) { - logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!"); - channel_admin_group = this->server->group_manager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); + auto admin_group_id = this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_or(0); + auto admin_group = this->server->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, admin_group_id); + if(!admin_group) { + logError(this->getServerId(), "Missing servers default channel admin group {}. Using the default channel gropup.", admin_group_id); + admin_group = this->server->default_channel_group(); } - /* FIXME: Log group assignment */ - this->server->group_manager()->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel); + /* admin_group might still be null since default_channel_group() could return nullptr */ + if(admin_group) { + this->server->group_manager()->assignments().set_channel_group(this->getClientDatabaseId(), created_channel->channelId(), admin_group->group_id(), false); + serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove( + this->getServerId(), this->server->getServerRoot(), + log::GroupTarget::CHANNEL, + admin_group->group_id(), admin_group->display_name(), + this->getClientDatabaseId(), this->getDisplayName() + ); + } if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEASPEAK)) { channel_tree_read_lock.unlock(); diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index add0da0..f3915dd 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -20,6 +20,8 @@ #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include "../../manager/ActionLogger.h" +#include "../../groups/GroupManager.h" +#include "../../absl/btree/map.h" #include #include #include @@ -443,28 +445,41 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto serverGroup = this->server->group_manager()->findGroup(cmd["cgid"].as()); - if (!serverGroup && cmd["cgid"].as() == 0) - serverGroup = this->server->group_manager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); + auto target_channel_group_id = cmd["cgid"].as(); + std::shared_ptr target_channel_group{}, default_channel_group{}; - if (!serverGroup || serverGroup->target() != GROUPTARGET_CHANNEL) - return command_result{error::group_invalid_id}; + default_channel_group = this->server->default_channel_group(); + if(target_channel_group_id == 0) { + target_channel_group = default_channel_group; + if(!target_channel_group) { + return command_result{error::vs_critical}; + } + } else { + target_channel_group = this->server->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); + if(!target_channel_group) { + return command_result{error::group_invalid_id}; + } + } - shared_lock server_channel_lock(this->server->channel_tree_lock); /* ensure we dont get moved or somebody could move us */ + shared_lock server_channel_lock{this->server->channel_tree_lock}; /* ensure we dont get moved or somebody could move us */ auto channel_id = cmd["cid"].as(); auto channel = this->server->channelTree->findChannel(channel_id); - if (!channel) return command_result{error::channel_invalid_id}; + if (!channel) { + return command_result{error::channel_invalid_id}; + } auto target_cldbid = cmd["cldbid"].as(); { auto channel_group_member_add_power = this->calculate_permission(permission::i_channel_group_member_add_power, channel_id); - if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_member_add_power, true)) { - if(target_cldbid != this->getClientDatabaseId()) + if(!target_channel_group->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_member_add_power, true)) { + if(target_cldbid != this->getClientDatabaseId()) { return command_result{permission::i_channel_group_member_add_power}; + } auto channel_group_self_add_power = this->calculate_permission(permission::i_channel_group_self_add_power, channel_id); - if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_self_add_power, true)) + if(!target_channel_group->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_self_add_power, true)) { return command_result{permission::i_channel_group_self_add_power}; + } } @@ -474,66 +489,68 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) if(client_needed_permission_modify_power.has_value) { - if(!permission::v2::permission_granted(client_needed_permission_modify_power, client_permission_modify_power)) + if(!permission::v2::permission_granted(client_needed_permission_modify_power, client_permission_modify_power)) { return command_result{permission::i_client_permission_modify_power}; - } - } - - std::shared_ptr old_group; - { - old_group = this->server->group_manager()->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(!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(!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->group_manager()->setChannelGroup(target_cldbid, serverGroup, channel); + auto old_group_id = this->server->group_manager()->assignments().exact_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, target_cldbid, channel->channelId()); + std::shared_ptr old_group{}; + + if(old_group_id.has_value()) { + old_group = this->server->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, old_group_id->group_id); + if(old_group) { + auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id); + if(!old_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(!old_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->group_manager()->assignments().set_channel_group(target_cldbid, target_channel_group->group_id(), channel->channelId(), !target_channel_group->is_permanent()); std::shared_ptr 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->group_manager()->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */ - client_channel_lock_w.unlock(); - shared_lock client_channel_lock_r(targetClient->channel_lock); - auto result = this->server->notifyClientPropertyUpdates(targetClient, updates); - if (result) { - targetClient->task_update_needed_permissions.enqueue(); + bool channel_group_changed, server_group_changed; + targetClient->update_displayed_client_groups(server_group_changed, channel_group_changed); - if(targetClient->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] == channel->channelId()) { //Only if group assigned over the channel - for (const auto &viewer : this->server->getClients()) { - /* if in view will be tested within that method */ - shared_lock viewer_channel_lock(viewer->channel_lock, defer_lock); - if(viewer != targetClient) - viewer_channel_lock.lock(); - viewer->notifyClientChannelGroupChanged(this->ref(), targetClient, targetClient->getChannel(), channel, serverGroup, false); + if(channel_group_changed) { + targetClient->task_update_needed_permissions.enqueue(); + targetClient->task_update_displayed_groups.enqueue(); + + for (const auto &viewer : this->server->getClients()) { + /* if in view will be tested within that method */ + std::shared_lock viewer_channel_lock{viewer->channel_lock, defer_lock}; + if(viewer != targetClient) { + viewer_channel_lock.lock(); } + viewer->notifyClientChannelGroupChanged(this->ref(), targetClient, targetClient->getChannel(), channel, target_channel_group, false); } } - - targetClient->task_update_channel_client_properties.enqueue(); } 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(), + old_group->group_id(), old_group->display_name(), target_cldbid, connected_client ? connected_client->getDisplayName() : "" ); } - if(serverGroup != this->server->group_manager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL)) { + + if(target_channel_group != default_channel_group) { serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, - serverGroup->groupId(), serverGroup->name(), + target_channel_group->group_id(), target_channel_group->display_name(), target_cldbid, connected_client ? connected_client->getDisplayName() : "" ); } @@ -1241,8 +1258,8 @@ void check_token_action_permissions(const std::shared_ptr& clie switch(action.type) { case ActionType::AddServerGroup: { - auto target_group = client->getServer()->group_manager()->findGroup(action.id1); - if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) { + auto target_group = client->getServer()->group_manager()->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, action.id1); + if(!target_group || target_group->group_type() == groups::GroupType::GROUP_TYPE_TEMPLATE) { action.type = ActionType::ActionIgnore; result.set_result(index, ts::command_result{error::group_invalid_id}); break; @@ -1258,8 +1275,8 @@ void check_token_action_permissions(const std::shared_ptr& clie } case ActionType::RemoveServerGroup: { - auto target_group = client->getServer()->group_manager()->findGroup(action.id1); - if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) { + auto target_group = client->getServer()->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, action.id1); + if(!target_group || target_group->group_type() == groups::GroupType::GROUP_TYPE_TEMPLATE) { action.type = ActionType::ActionIgnore; result.set_result(index, ts::command_result{error::group_invalid_id}); break; @@ -1275,8 +1292,8 @@ void check_token_action_permissions(const std::shared_ptr& clie } case ActionType::SetChannelGroup: { - auto target_group = client->getServer()->group_manager()->findGroup(action.id1); - if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_CHANNEL) { + auto target_group = client->getServer()->group_manager()->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, action.id1); + if(!target_group || target_group->group_type() == groups::GroupType::GROUP_TYPE_TEMPLATE) { action.type = ActionType::ActionIgnore; result.set_result(index, ts::command_result{error::group_invalid_id}); break; @@ -2029,8 +2046,10 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) { } return 0; }); - if(entries.empty()) + + if(entries.empty()) { return command_result{error::database_empty_result}; + } struct CommandPerm { permission::PermissionType p; @@ -2044,7 +2063,16 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) { perms.resize(entries.size()); size_t index{0}; - auto all_groups = this->server->group_manager()->availableGroups(true); + + /* 1 := Server | 2 := Channel */ + btree::map group_types{}; + for(const auto& s_group : this->server->group_manager()->server_groups()->available_groups(groups::GroupCalculateMode::GLOBAL)) { + group_types[s_group->group_id()] = 1; + } + for(const auto& c_group : this->server->group_manager()->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL)) { + group_types[c_group->group_id()] = 2; + } + for(const auto& entry : entries) { auto& perm = perms[index++]; @@ -2090,19 +2118,24 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) { perm.id2 = 0; perm.t = 2; /* channel permission */ } else if(entry->type == permission::SQL_PERM_GROUP) { - auto group = std::find_if(all_groups.begin(), all_groups.end(), [&](const auto& group) { return group->groupId() == entry->group_id; }); - if(group == all_groups.end()) { - index--; /* unknown group */ - continue; - } - if((*group)->target() == GroupTarget::GROUPTARGET_CHANNEL) { - perm.id1 = 0; - perm.id2 = entry->group_id; - perm.t = 3; /* channel group */ - } else { - perm.id1 = entry->group_id; - perm.id2 = 0; - perm.t = 0; /* server group */ + auto group_type = group_types[entry->group_id]; + switch(group_type) { + case 1: + perm.id1 = entry->group_id; + perm.id2 = 0; + perm.t = 0; /* server group */ + break; + + case 2: + perm.id1 = 0; + perm.id2 = entry->group_id; + perm.t = 3; /* channel group */ + break; + + case 0: + default: + /* unknown group */ + break; } } #endif @@ -2128,21 +2161,6 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) { return &a > &b; }); -#if 0 - Command result(this->notify_response_command("notifypermfind")); - index = 0; - - // http://yat.qa/ressourcen/server-query-kommentare/#permfind - for(const auto& e : perms) { - result[index]["p"] = e.p; - result[index]["v"] = e.v; - result[index]["id1"] = e.id1; - result[index]["id2"] = e.id2; - result[index]["t"] = e.t; - index++; - } - this->sendCommand(result); -#else command_builder result{this->notify_response_command("notifypermfind"), 64, perms.size()}; index = 0; @@ -2155,7 +2173,6 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) { bulk.put("id2", e.id2); } this->sendCommand(result); -#endif return command_result{error::ok}; } @@ -2171,7 +2188,9 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); auto client_dbid = cmd["cldbid"].as(); - if(!serverInstance->databaseHelper()->validClientDatabaseId(this->getServer(), client_dbid)) return command_result{error::client_invalid_id}; + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->getServer(), client_dbid)) { + return command_result{error::client_invalid_id}; + } if(client_dbid == this->getClientDatabaseId()) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_own, 1); @@ -2182,26 +2201,28 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { string channel_query, perm_query; auto channel = this->server ? this->server->channelTree->findChannel(cmd["cid"]) : serverInstance->getChannelTree()->findChannel(cmd["cid"]); - if(!channel) return command_result{error::channel_invalid_id}; + if(!channel) { + return command_result{error::channel_invalid_id}; + } - auto server_groups = this->server->group_manager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK); - auto channel_group = this->server->group_manager()->getChannelGroup(client_dbid, channel, true); + auto channel_group = this->assignedChannelGroup(channel); auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), client_dbid); Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : ""); size_t index = 0; result["cldbid"] = client_dbid; result["cid"] = channel->channelId(); - if(cmd["return_code"].size() > 0) + if(cmd["return_code"].size() > 0) { result["return_code"] = cmd["return_code"].string(); + } - for(const auto& server_group : server_groups) { - auto permission_manager = server_group->group->permissions(); + for(const auto& server_group : this->assignedServerGroups()) { + auto permission_manager = server_group->permissions(); for(const auto& permission_data : permission_manager->permissions()) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 0; /* server group */ - result[index]["id1"] = server_group->group->groupId(); + result[index]["id1"] = server_group->group_id(); result[index]["id2"] = 0; result[index]["p"] = std::get<0>(permission_data); @@ -2212,7 +2233,7 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { } if(permission.flags.grant_set) { result[index]["t"] = 0; /* server group */ - result[index]["id1"] = server_group->group->groupId(); + result[index]["id1"] = server_group->group_id(); result[index]["id2"] = 0; result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); diff --git a/server/src/groups/Group.h b/server/src/groups/Group.h index 1a97aee..aa8d87f 100644 --- a/server/src/groups/Group.h +++ b/server/src/groups/Group.h @@ -5,17 +5,17 @@ namespace ts::server::groups { enum GroupType { - GROUP_TYPE_QUERY, - GROUP_TYPE_TEMPLATE, - GROUP_TYPE_NORMAL, + GROUP_TYPE_TEMPLATE = 0x00, + GROUP_TYPE_NORMAL = 0x01, + GROUP_TYPE_QUERY = 0x02, GROUP_TYPE_UNKNOWN = 0xFF }; enum GroupNameMode { - GROUP_NAME_MODE_HIDDEN, - GROUP_NAME_MODE_BEFORE, - GROUP_NAME_MODE_BEHIND + GROUP_NAME_MODE_HIDDEN = 0x00, + GROUP_NAME_MODE_BEFORE = 0x01, + GROUP_NAME_MODE_BEHIND = 0x02 }; typedef uint32_t GroupSortId; diff --git a/server/src/groups/GroupAssignmentManager.cpp b/server/src/groups/GroupAssignmentManager.cpp index e71f889..4658f2a 100644 --- a/server/src/groups/GroupAssignmentManager.cpp +++ b/server/src/groups/GroupAssignmentManager.cpp @@ -4,6 +4,7 @@ #include #include "./GroupAssignmentManager.h" #include "./GroupManager.h" +#include "BasicChannel.h" using namespace ts::server::groups; @@ -217,7 +218,7 @@ std::vector GroupAssignmentManager::server_groups_of_client(ts::ser return result; } -std::vector GroupAssignmentManager::channel_groups_of_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) { +std::vector GroupAssignmentManager::exact_channel_groups_of_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) { std::vector result{}; bool cache_found{false}; { @@ -262,7 +263,7 @@ std::vector GroupAssignmentManager::channel_groups_of_cl if(mode == GroupAssignmentCalculateMode::GLOBAL) { if(auto parent = this->manager_->parent_manager(); parent) { - auto parent_groups = parent->assignments().channel_groups_of_client(mode, cldbid); + auto parent_groups = parent->assignments().exact_channel_groups_of_client(mode, cldbid); result.reserve(result.size() + parent_groups.size()); result.insert(result.begin(), parent_groups.begin(), parent_groups.end()); } @@ -271,10 +272,10 @@ std::vector GroupAssignmentManager::channel_groups_of_cl return result; } -std::optional GroupAssignmentManager::channel_group_of_client(GroupAssignmentCalculateMode mode, - ClientDbId client_database_id, ChannelId channel_id) { +std::optional GroupAssignmentManager::exact_channel_group_of_client(GroupAssignmentCalculateMode mode, + ClientDbId client_database_id, ChannelId channel_id) { /* TODO: Improve performance by not querying all groups */ - auto assignments = this->channel_groups_of_client(mode, client_database_id); + auto assignments = this->exact_channel_groups_of_client(mode, client_database_id); for(const auto& assignment : assignments) { if(assignment.channel_id != channel_id) { continue; @@ -286,6 +287,29 @@ std::optional GroupAssignmentManager::channel_group_of_c return std::nullopt; } +std::optional GroupAssignmentManager::calculate_channel_group_of_client(GroupAssignmentCalculateMode mode, + ClientDbId client_database_id, + std::shared_ptr &channel) { + auto assignments = this->exact_channel_groups_of_client(mode, client_database_id); + while(channel) { + for(const auto& assignment : assignments) { + if(assignment.channel_id != channel->channelId()) { + continue; + } + + return std::make_optional(assignment.group_id); + } + + if(permission::v2::permission_granted(1, channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end))) { + break; + } + + channel = channel->parent(); + } + + return std::nullopt; +} + std::deque GroupAssignmentManager::server_group_clients(GroupId group_id) { std::deque result{}; if constexpr(kCacheAllClients) { @@ -330,6 +354,59 @@ std::deque GroupAssignmentManager::server_group_clients(GroupId return result; } +std::deque> GroupAssignmentManager::channel_group_list( + GroupId group_id, + ChannelId channel_id, + ClientDbId client_database_id +) { + std::string sql_query{}; + sql_query += "SELECT `groupId`, `cldbid`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid"; + if(group_id > 0) { + sql_query += " AND `groupId` = :groupId"; + } + if(channel_id > 0) { + sql_query += " AND `channelId` = :cid"; + } + if(client_database_id > 0) { + sql_query += " AND `cldbid` = :cldbid"; + } + sql::command sql{this->sql_manager(), sql_query}; + sql.value(":groupId", group_id); + sql.value(":cid", channel_id); + sql.value(":cldbid", client_database_id); + + std::deque> result{}; + LOG_SQL_CMD(sql.query([&](int length, std::string* values, std::string* names) { + GroupId group_id; + ChannelId channel_id; + ClientDbId client_database_id; + + int index{0}; + try { + assert(names[index] == "groupId"); + group_id = std::stoull(values[index++]); + + assert(names[index] == "cldbid"); + channel_id = std::stoull(values[index++]); + + assert(names[index] == "channelId"); + client_database_id = std::stoull(values[index++]); + + assert(index == length); + } catch (std::exception& ex) { + logError(this->server_id(), "Failed to parse client group assignment at index {}: {}", + index - 1, + ex.what() + ); + return; + } + + result.emplace_back(group_id, channel_id, client_database_id); + })); + + return result; +} + GroupAssignmentResult GroupAssignmentManager::add_server_group(ClientDbId client, GroupId group) { bool cache_verified{false}; { @@ -340,8 +417,9 @@ GroupAssignmentResult GroupAssignmentManager::add_server_group(ClientDbId client auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) { return assignment.group_id == group; }); - if(it != entry->server_group_assignments.end()) + if(it != entry->server_group_assignments.end()) { return GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP; + } entry->server_group_assignments.emplace_back(group); cache_verified = true; break; @@ -384,8 +462,9 @@ GroupAssignmentResult GroupAssignmentManager::remove_server_group(ClientDbId cli auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) { return assignment.group_id == group; }); - if(it == entry->server_group_assignments.end()) + if(it == entry->server_group_assignments.end()) { return GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP; + } entry->server_group_assignments.erase(it); cache_verified = true; break; @@ -507,8 +586,14 @@ void GroupAssignmentManager::cleanup_channel_temporary_assignment(ClientDbId cli auto assignment = std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) { return assignment.channel_id == channel; }); - if(assignment->temporary_assignment) + + if(assignment == client->channel_group_assignments.end()) { + break; + } + + if(assignment->temporary_assignment) { client->channel_group_assignments.erase(assignment); + } break; } } diff --git a/server/src/groups/GroupAssignmentManager.h b/server/src/groups/GroupAssignmentManager.h index d438b5a..9ef2462 100644 --- a/server/src/groups/GroupAssignmentManager.h +++ b/server/src/groups/GroupAssignmentManager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -12,6 +13,10 @@ namespace sql { class SqlManager; } +namespace ts { + class BasicChannel; +} + namespace ts::server { class ConnectedClient; @@ -63,17 +68,30 @@ namespace ts::server { bool load_data(std::string& /* error */); void unload_data(); + void reset_all(); + /* client specific cache methods */ void enable_cache_for_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */); void disable_cache_for_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */); /* info/query methods */ [[nodiscard]] std::vector server_groups_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */); - [[nodiscard]] std::vector channel_groups_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */); - [[nodiscard]] std::optional channel_group_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */, ChannelId /* channel id */); + [[nodiscard]] std::vector exact_channel_groups_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */); + [[nodiscard]] std::optional exact_channel_group_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */, ChannelId /* channel id */); + + /** + * Calculate the target channel group for the client. + * The parameters `target channel` will contain the channel where the group has been inherited from. + * Note: `target channel` will be altered if the resutl is empty. + * @return The target channel group id + */ + [[nodiscard]] std::optional calculate_channel_group_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */, std::shared_ptr& /* target channel */); [[nodiscard]] std::deque server_group_clients(GroupId /* group id */); - //[[nodiscard]] std::deque channel_group_clients(GroupId /* group id */, ChannelId /* channel id */); + [[nodiscard]] std::deque> channel_group_list(GroupId /* group id */, ChannelId /* channel id */, ClientDbId /* client database id */); + + [[nodiscard]] bool is_server_group_empty(GroupId /* group id */); + [[nodiscard]] bool is_channel_group_empty(GroupId /* group id */); /* change methods */ GroupAssignmentResult add_server_group(ClientDbId /* client database id */, GroupId /* group id */); @@ -84,6 +102,10 @@ namespace ts::server { void cleanup_assignments(); void cleanup_channel_assignments(ChannelId /* channel */); void cleanup_channel_temporary_assignment(ClientDbId /* client database id */, ChannelId /* channel */); + + void handle_channel_deleted(ChannelId /* channel id */); + void handle_server_group_deleted(GroupId /* group id */); + void handle_channel_group_deleted(GroupId /* group id */); private: struct ClientCache { ClientDbId client_database_id{0}; diff --git a/server/src/groups/GroupManager.cpp b/server/src/groups/GroupManager.cpp index 386c050..a3b6930 100644 --- a/server/src/groups/GroupManager.cpp +++ b/server/src/groups/GroupManager.cpp @@ -114,6 +114,27 @@ void AbstractGroupManager::unload_data() { } } +void AbstractGroupManager::reset_groups(const std::shared_ptr &template_provider, std::map &mapping) { + std::lock_guard manage_lock{this->group_manage_mutex_}; + this->unload_data(); + + /* Delete all old groups */ + { + /* FIXME: Only delete groups with our database target! */ + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type", + variable{":serverId", this->server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}).execute()); + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", + variable{":serverId", this->server_id()}).execute()); + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId", + variable{":serverId", this->server_id()}).execute()); + } + + if(auto error = this->load_data(true); error != GroupLoadResult::SUCCESS) { + logCritical(this->server_id(), "Failed to load groups after group unload ({}). There might be no groups loaded now!", (int) error); + } +} + void AbstractGroupManager::reset_groups(bool db_cleanup) { std::lock_guard manage_lock{this->group_manage_mutex_}; this->unload_data(); @@ -224,6 +245,12 @@ std::shared_ptr AbstractGroupManager::find_group_by_name_(GroupCalculateM } GroupCreateResult AbstractGroupManager::create_group_(GroupType type, const std::string &name, std::shared_ptr& result) { + if(name.empty()) { + return GroupCreateResult::NAME_TOO_SHORT; + } else if(name.length() > 30) { + return GroupCreateResult::NAME_TOO_LONG; + } + std::lock_guard manage_lock{this->group_manage_mutex_}; if(this->find_group_by_name_(GroupCalculateMode::LOCAL, name)) { return GroupCreateResult::NAME_ALREADY_IN_USED; @@ -264,9 +291,6 @@ GroupCopyResult AbstractGroupManager::copy_group_(GroupId source, GroupType targ case GroupCreateResult::NAME_ALREADY_IN_USED: return GroupCopyResult::NAME_ALREADY_IN_USE; - - case GroupCreateResult::FAILED_TO_GENERATE_ID: - return GroupCopyResult::FAILED_TO_GENERATE_ID; } assert(result); @@ -335,12 +359,17 @@ GroupDeleteResult AbstractGroupManager::delete_group_(GroupId group_id) { std::lock_guard manage_lock{this->group_manage_mutex_}; { - std::lock_guard glock{this->group_mutex_}; + std::unique_lock glock{this->group_mutex_}; auto it = std::find_if(this->groups_.begin(), this->groups_.begin(), [&](const std::shared_ptr& group) { return group->group_id() == group_id; }); if(it == this->groups_.end()) { + if(this->parent_manager_) { + glock.unlock(); + return this->parent_manager_->delete_group_(group_id); + } + return GroupDeleteResult::INVALID_GROUP_ID; } @@ -354,6 +383,9 @@ GroupDeleteResult AbstractGroupManager::delete_group_(GroupId group_id) { return GroupDeleteResult::SUCCESS; } +void AbstractGroupManager::reset_groups_(const std::shared_ptr &source, std::map &mapping) { + +} /* Server group manager */ ServerGroupManager::ServerGroupManager(const std::shared_ptr &handle, std::shared_ptr parent) diff --git a/server/src/groups/GroupManager.h b/server/src/groups/GroupManager.h index 967d3f9..bcbcb76 100644 --- a/server/src/groups/GroupManager.h +++ b/server/src/groups/GroupManager.h @@ -24,7 +24,8 @@ namespace ts::server::groups { enum struct GroupCreateResult { SUCCESS, NAME_ALREADY_IN_USED, - FAILED_TO_GENERATE_ID, + NAME_TOO_SHORT, + NAME_TOO_LONG, DATABASE_ERROR }; @@ -33,7 +34,6 @@ namespace ts::server::groups { UNKNOWN_SOURCE_GROUP, UNKNOWN_TARGET_GROUP, NAME_ALREADY_IN_USE, - FAILED_TO_GENERATE_ID, DATABASE_ERROR }; @@ -73,9 +73,14 @@ namespace ts::server::groups { bool initialize(std::string& /* error */); GroupLoadResult load_data(bool /* initialize */ = false); void unload_data(); - void reset_groups(bool /* cleanup database */); void save_permissions(size_t& /* total groups */, size_t& /* saved groups */); + + /** + * Reset all known groups. + * If the template group provider is empty no new groups will be created. + */ + void reset_groups(const std::shared_ptr& /* template group provider */, std::map& /* mapping */); protected: std::shared_ptr parent_manager_; DatabaseGroupTarget database_target_; diff --git a/server/things_to_test b/server/things_to_test new file mode 100644 index 0000000..9bc9ca5 --- /dev/null +++ b/server/things_to_test @@ -0,0 +1,13 @@ +permission calculation +channel group inheritance +general server & channel groups +- assignments +- removement +- temporary groups + +Notified channel & server member add/remove powers +Group edit if the notify group triggers + +channelgroupclientlist + +TODO: Delete the group header