diff --git a/CMakeLists.txt b/CMakeLists.txt index eb0d0f0..2a18688 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(TEASPEAK_SERVER ON) #end now #set(MEMORY_DEBUG_FLAGS " -fsanitize=leak -fsanitize=address -fstack-protector-all ") -#set(MEMORY_DEBUG_FLAGS "-fsanitize=address -fstack-protector-all") +set(MEMORY_DEBUG_FLAGS "-fsanitize=address -fstack-protector-all") #set(MEMORY_DEBUG_FLAGS "-fstack-protector-all") #set(MEMORY_DEBUG_FLAGS " -fsanitize=address -static-libasan") diff --git a/file/local_server/LocalFileTransferDisk.cpp b/file/local_server/LocalFileTransferDisk.cpp index 18aff29..14997ec 100644 --- a/file/local_server/LocalFileTransferDisk.cpp +++ b/file/local_server/LocalFileTransferDisk.cpp @@ -29,8 +29,9 @@ void LocalFileTransfer::shutdown_disk_io() { { std::unique_lock qlock{this->disk_io.queue_lock}; this->disk_io.notify_work_awaiting.notify_all(); - while(this->disk_io.queue_head) + while(this->disk_io.queue_head) { this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10}); + } if(this->disk_io.queue_head) { logWarning(0, "Failed to flush disk IO. Force aborting."); diff --git a/git-teaspeak b/git-teaspeak index d0babbc..8c2608f 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit d0babbc62fb657783a04737a1265ab1c36636bf6 +Subproject commit 8c2608f90739a42cd727c8c8eab3d60bbb6dca53 diff --git a/license/server/DatabaseHandler.cpp b/license/server/DatabaseHandler.cpp index 9038ff2..0c191e9 100644 --- a/license/server/DatabaseHandler.cpp +++ b/license/server/DatabaseHandler.cpp @@ -117,12 +117,12 @@ bool DatabaseHandler::setup(std::string& error) { SET_VERSION(6); case 6: - CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);"); + CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` VARCHAR(32), `succeeded` TINYINT);"); CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)"); SET_VERSION(7); default:; - } + } return true; } diff --git a/rtclib b/rtclib index cdf42fc..ea2dd19 160000 --- a/rtclib +++ b/rtclib @@ -1 +1 @@ -Subproject commit cdf42fccd34c769fabfef7208f829c3f4b62a595 +Subproject commit ea2dd197d26330850dfef795cf7db140d26194d7 diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index fa78cbb..388be22 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -56,6 +56,7 @@ set(SERVER_SOURCE_FILES src/server/VoiceServer.cpp src/server/POWHandler.cpp src/client/voice/VoiceClientConnection.cpp + src/client/command_handler/groups.cpp src/client/command_handler/channel.cpp src/client/command_handler/client.cpp src/client/command_handler/server.cpp @@ -223,7 +224,7 @@ target_link_libraries(PermMapHelper SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "5") -SET(CPACK_PACKAGE_VERSION_PATCH "2") +SET(CPACK_PACKAGE_VERSION_PATCH "3") if (BUILD_TYPE_NAME EQUAL OFF) SET(CPACK_PACKAGE_VERSION_DATA "beta") elseif (BUILD_TYPE_NAME STREQUAL "") diff --git a/server/src/DatabaseHelper.cpp b/server/src/DatabaseHelper.cpp index 29b56f4..6eff315 100644 --- a/server/src/DatabaseHelper.cpp +++ b/server/src/DatabaseHelper.cpp @@ -172,8 +172,6 @@ inline sql::result load_permissions_v2( sql::command& command, bool test_channel, /* only used for client permissions (client channel permissions) */ bool is_channel) { - auto start = system_clock::now(); - return command.query([&](int length, char** values, char** names){ permission::PermissionType key = permission::PermissionType::undefined; permission::PermissionValue value = permNotGranted, granted = permNotGranted; @@ -214,10 +212,11 @@ inline sql::result load_permissions_v2( return 0; } - if(channel_id == 0 || is_channel) + if(channel_id == 0 || is_channel) { manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted); - else + } else { manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted); + } return 0; }); @@ -387,7 +386,7 @@ std::shared_ptr DatabaseHelper::loadGroupPerm return result; } -void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId group_id, uint8_t /* target */, const std::shared_ptr &permissions) { +void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId group_id, uint8_t target, const std::shared_ptr &permissions) { const auto updates = permissions->flush_db_updates(); if(updates.empty()) return; @@ -398,8 +397,8 @@ void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId auto permission_data = permission::resolvePermissionData(update.permission); auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; - logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}", - group_id, + logTrace(server_id, "Updating group permission for group {}/{}: {}. New value: {}. New grant: {}. Query: {}", + target, group_id, permission_data->name, value, grant, @@ -608,9 +607,7 @@ bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, s return false; debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId()); - return true; } - return true; } diff --git a/server/src/Group.cpp b/server/src/Group.cpp index 19ed935..d175aeb 100644 --- a/server/src/Group.cpp +++ b/server/src/Group.cpp @@ -12,961 +12,3 @@ using namespace std::chrono; using namespace ts; using namespace ts::server; using namespace ts::permission; - -extern InstanceHandler* serverInstance; -Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId) : _target(target), _type(type) { - memtrack::allocated(this); - this->handle = handle; - - this->_properties = std::make_shared(); - this->_properties->register_property_type(); - this->setPermissionManager(make_shared()); - - this->properties()[property::GROUP_ID] = groupId; - this->properties()[property::GROUP_TYPE] = type; - /* - this->_properties->registerProperty("sgid", groupId, PROP_GROUP_INIT_SERVER); - this->_properties->registerProperty("cgid", groupId, PROP_GROUP_INIT_CHANNEL); - this->_properties->registerProperty("gid", groupId, PROP_PRIVATE_TEMP); - - - this->_properties->registerProperty("type", (uint8_t) type, PROP_GROUP_INIT); - this->_properties->registerProperty("name", "Undefined group #" + to_string(groupId), PROP_GROUP_INIT | PROP_SNAPSHOT); - this->_properties->registerProperty("sortid", 0, PROP_GROUP_INIT); - this->_properties->registerProperty("savedb", 0, PROP_GROUP_INIT); - this->_properties->registerProperty("namemode", 0, PROP_GROUP_INIT); - this->_properties->registerProperty("iconid", 0, PROP_GROUP_INIT); - - this->_properties->registerProperty("default_group", false, PROP_PRIVATE_TEMP); - */ - - this->_properties->registerNotifyHandler([&](Property& prop){ - if((prop.type().flags & property::FLAG_SAVE) == 0) return; - if(!this->handle) return; - - std::string sql; - if(prop.hasDbReference()){ - sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid AND `key` = :key"; - } else { - prop.setDbReference(true); - sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :gid, :key, :value)"; - } - - sql::command(this->handle->sql, sql, - variable{":sid", this->handle->getServerId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}, variable{":gid", this->groupId()}, variable{":key", prop.type().name}, variable{":value", prop.value()}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); - }); -} - -void Group::setPermissionManager(const std::shared_ptr &manager) { - this->_permissions = manager; - this->apply_properties_from_permissions(); -} - -void Group::apply_properties_from_permissions() { - { - auto permission = this->_permissions->permission_value_flagged(permission::i_icon_id); - this->properties()[property::GROUP_ICONID] = permission.has_value ? permission.value : 0; - } - { - auto permission = this->_permissions->permission_value_flagged(permission::i_group_show_name_in_tree); - this->properties()[property::GROUP_NAMEMODE] = permission.has_value ? permission.value : 0; - } - { - auto permission = this->_permissions->permission_value_flagged(permission::i_group_sort_id); - this->properties()[property::GROUP_SORTID] = permission.has_value ? permission.value : 0; - } - { - auto permission = this->_permissions->permission_value_flagged(permission::b_group_is_permanent); - this->properties()[property::GROUP_SAVEDB] = permission.has_value ? permission.value : 0; - } -} - -Group::~Group() { - memtrack::freed(this); -} - -GroupManager::GroupManager(const shared_ptr &server, sql::SqlManager *sql, std::shared_ptr root) : server(server), sql(sql), root(std::move(root)) { } - -GroupManager::~GroupManager() {} - -bool GroupManager::loadGroupFormDatabase(GroupId id) { - std::lock_guard glock{this->group_lock}; - if(id == 0){ - this->groups.clear(); - - auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&GroupManager::insertGroupFromDb, this); - LOG_SQL_CMD(res); - - return true; - } else { - for(const auto &e : this->groups) - if(e->groupId() == id){ - this->groups.erase(find(this->groups.begin(), this->groups.end(), e)); - break; - } - auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", - variable{":sid", this->getServerId()}, variable{":gid", id}).query(&GroupManager::insertGroupFromDb, this); - auto fn = LOG_SQL_CMD; - fn(res); - return res; - } -} - -std::vector> GroupManager::availableGroups(bool root) { - std::vector> response; - - { - std::lock_guard glock{this->group_lock}; - for(const auto& group : this->groups) - response.push_back(group); - } - - if(root && this->root){ - auto elm = this->root->availableGroups(); - for(const auto& e : elm) - response.push_back(e); - } - return response; -} - -std::vector> GroupManager::availableServerGroups(bool root){ - std::vector> response; - - { - std::lock_guard glock{this->group_lock}; - for(const auto& group : this->groups) - if(group->target() == GroupTarget::GROUPTARGET_SERVER) - response.push_back(group); - } - - if(root && this->root){ - auto elm = this->root->availableServerGroups(); - for(const auto& e : elm) - response.push_back(e); - } - return response; -} - -std::vector> GroupManager::availableChannelGroups(bool root) { - std::vector> response; - { - std::lock_guard glock{this->group_lock}; - for(const auto& group : this->groups) - if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) - response.push_back(group); - } - if(root && this->root){ - auto elm = this->root->availableChannelGroups(true); - for(const auto& e : elm) - response.push_back(e); - } - return response; -} - -int GroupManager::insertGroupFromDb(int count, char **values, char **column) { - GroupId groupId = 0; - auto target = (GroupTarget) 0xff; - auto type = (GroupType) 0xff; - std::string targetName; - - for(int index = 0; index < count; index++){ - if(strcmp(column[index], "target") == 0) - target = (GroupTarget) stoll(values[index]); - else if(strcmp(column[index], "type") == 0) - type = (GroupType) stoll(values[index]); - else if(strcmp(column[index], "groupId") == 0) - groupId = (GroupType) stoll(values[index]); - else if(strcmp(column[index], "displayName") == 0) - targetName = values[index]; - //else cerr << "Invalid group table row " << column[index] << endl; - } - - if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) { - logCritical(this->getServerId(), "Found invalid group ad database! (GroupId " + to_string(groupId) + ", Target " + to_string(target) + ", Type " + to_string(type) + ", Name '" + targetName + "')"); - return 0; - } - /* - assert(groupId != 0); - assert(target != 0xff); - assert(type != 0xff); - assert(!targetName.empty()); - */ - - shared_ptr group = std::make_shared(this, target, type, groupId); - - /* - auto res = sql::command(this->sql, "SELECT `key`, `value` FROM `properties` WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid", - variable{":sid", this->getServerId()}, variable{":type", property::PROP_TYPE_GROUP}, variable{":gid", group->groupId()}).query([this](Group* g, int length, char** values, char** columns){ - string key, value; - for(int index = 0; index < length; index++) - if(strcmp(columns[index], "key") == 0) - key = values[index]; - else if(strcmp(columns[index], "value") == 0) - value = values[index]; - - auto info = property::info(key); - if(info == property::GROUP_UNDEFINED) { - logError(this->getServerId(), "Invalid property for group: " + key); - return 0; - } - - auto prop = g->properties()[info.property]; - prop.setDbReference(true); - prop.value(value, false); - return 0; - }, group.get()); - auto print = LOG_SQL_CMD; - print(res); - */ - //FIXME load group properties view database helper (or drop it full because no saved properties) - - group->properties()[property::GROUP_NAME] = targetName; - - group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->getServerId(), group->groupId(), 0)); - - debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name()); - this->groups.push_back(group); - -#if 0 - auto iconId = (IconId) group->icon_id(); - if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) { - logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ")."); - if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing); - } -#endif - return 0; -} - -void GroupManager::handleChannelDeleted(const ChannelId& channel_id) { - unique_lock cache_lock(this->cacheLock); - auto cached_clients = std::vector>{this->cachedClients.begin(), this->cachedClients.end()}; - cache_lock.unlock(); - - for(auto& entry : cached_clients) { - lock_guard entry_lock(entry->lock); - entry->channel_groups.erase(channel_id); - } -} - -bool GroupManager::isLocalGroup(std::shared_ptr gr) { - std::lock_guard glock{this->group_lock}; - return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end(); -} - -std::shared_ptr GroupManager::defaultGroup(GroupTarget type, bool enforce_property) { - threads::MutexLock lock(this->cacheLock); - if(this->groups.empty()) return nullptr; - - auto server = this->server.lock(); - auto id = - server ? - server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP - : property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or(0) : - serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or(0); - auto group = this->findGroupLocal(id); - if(group || enforce_property) return group; - - { - std::lock_guard glock{this->group_lock}; - for(auto elm : this->groups) - if(elm->target() == type) - return elm; - } - - return nullptr; //Worst case! -} - -std::shared_ptr GroupManager::findGroup(GroupId groupId) { - auto result = this->findGroupLocal(groupId); - if(!result && this->root) result = this->root->findGroup(groupId); - return result; -} - -std::shared_ptr GroupManager::findGroupLocal(GroupId groupId) { - std::lock_guard glock{this->group_lock}; - for(const auto& elm : this->groups) - if(elm->groupId() == groupId) return elm; - return nullptr; -} - -std::vector> GroupManager::findGroup(GroupTarget target, std::string name) { - vector> res; - { - std::lock_guard glock{this->group_lock}; - for(const auto &elm : this->groups) - if(elm->name() == name && elm->target() == target) res.push_back(elm); - } - if(this->root) { - auto r = root->findGroup(target, name); - for(const auto &e : r) res.push_back(e); - } - return res; -} - -ServerId GroupManager::getServerId() { - auto l = this->server.lock(); - return l ? l->getServerId() : 0; -} - -std::shared_ptr GroupManager::createGroup(GroupTarget target, GroupType type, std::string name) { - if(type != GROUP_TYPE_NORMAL && this->root) return root->createGroup(target, type, name); - - auto rawId = generateGroupId(this->sql); - if(rawId <= 0) { - logError(this->getServerId(), "Could not create a new group! ({})", "Could not generate group id"); - return nullptr; - } - auto groupId = (GroupId) rawId; - auto res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", - variable{":sid", this->getServerId()}, variable{":gid", groupId}, variable{":target", target}, variable{":type", type}, variable{":name", name}).execute(); - auto print = LOG_SQL_CMD; - print(res); - if(!res) return nullptr; - - std::shared_ptr group = std::make_shared(this, target, type, groupId); - group->properties()[property::GROUP_NAME] = name; - group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->getServerId(), group->groupId(), 0)); - - std::lock_guard glock{this->group_lock}; - this->groups.push_back(group); - return group; -} - -GroupId GroupManager::copyGroup(std::shared_ptr group, GroupType type, std::string name, ServerId targetServerId) { - auto group_server = group->handle->getServerId(); - auto groupId = generateGroupId(this->sql); - - auto print = LOG_SQL_CMD; - auto res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", - variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", SQL_PERM_GROUP}).execute(); - print(res); - - //Properties not used currently - res = sql::command(this->sql, "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, `key`, `value` FROM `properties` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", - variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}).execute(); - print(res); - - res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", - variable{":sid", targetServerId}, variable{":gid", groupId}, variable{":target", group->target()}, variable{":type", type}, variable{":name", name}).execute(); - print(res); - - if(targetServerId == (this->getServerId())) - this->loadGroupFormDatabase(groupId); - else if(this->root) - this->root->loadGroupFormDatabase(groupId); - return groupId; -} - -bool GroupManager::copyGroupPermissions(const shared_ptr &source, const shared_ptr &target) { - auto targetServer = target->handle->getServerId(); - auto sourceServer = source->handle->getServerId(); - - auto res = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":id", target->groupId()}).execute(); - LOG_SQL_CMD(res); - - res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", - variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute(); - - target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->getServerId(), target->groupId(), 0)); - LOG_SQL_CMD(res); - return true; -} - -bool GroupManager::reloadGroupPermissions(std::shared_ptr group) { - if(!isLocalGroup(group)){ - if(this->root) return this->root->reloadGroupPermissions(group); - return false; - } - - group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->getServerId(), group->groupId(), 0)); - return true; -} - -bool GroupManager::renameGroup(std::shared_ptr group, std::string name) { - if(!isLocalGroup(group)){ - if(this->root) return this->root->renameGroup(group, name); - return false; - } - - // CREATE_TABLE("groups", "`serverId` INT NOT NULL, `groupId` INTEGER, `target` INT, `type` INT, `displayName` TEXT"); - group->properties()[property::GROUP_NAME] = name; - sql::command(this->sql, "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :sid AND `groupId` = :gid AND `target` = :target", - variable{":name", name}, variable{":gid", group->groupId()}, variable{":target", group->target()}, variable{":sid", this->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed"}); - return true; -} - -bool GroupManager::deleteAllGroups() { - LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute()); - LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute()); - LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", this->getServerId()}, variable{":type", SQL_PERM_GROUP}).execute()); - { - lock_guard cache_lock(this->cacheLock); - for(const auto& entry : this->cachedClients) { - lock_guard entry_lock(entry->lock); - entry->server_groups.clear(); - entry->channel_groups.clear(); - } - } - this->groups.clear(); - return true; -} - -bool GroupManager::deleteGroup(std::shared_ptr group) { - if(!isLocalGroup(group)){ - if(this->root) return this->root->deleteGroup(group); - return false; - } - - { - std::lock_guard glock{this->group_lock}; - this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group)); - } - - /* erase the group out of our cache */ - { - lock_guard cache_lock(this->cacheLock); - for(auto& entry : this->cachedClients) { - lock_guard entry_lock(entry->lock); - - entry->server_groups.erase(std::remove_if(entry->server_groups.begin(), entry->server_groups.end(), [&](const std::shared_ptr& group_assignment) { - return group_assignment->group == group; - }), entry->server_groups.end()); - - for(auto it = entry->channel_groups.begin(); it != entry->channel_groups.end();) { - if(it->second->group == group) - it = entry->channel_groups.erase(it); - else - it++; - } - } - } - - bool flag_sql = false; - auto res = sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute(); - LOG_SQL_CMD(res); - flag_sql |= !res; - - res = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute(); - LOG_SQL_CMD(res); - flag_sql |= !res; - - serverInstance->databaseHelper()->deleteGroupArtifacts(this->getServerId(), group->groupId()); - - if(flag_sql) - logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId()); - - return true; -} - -int64_t GroupManager::generateGroupId(sql::SqlManager* sql) { - int64_t hightestGroupId = 0; - sql::command(sql, "SELECT `groupId` FROM `groups` ORDER BY `groupId` DESC LIMIT 1").query([](int64_t* ptr, int, char** values, char**){ - *ptr = stoul(values[0]); - return 0; - }, &hightestGroupId); - return hightestGroupId + 1; -} - -std::deque GroupManager::update_server_group_property(const shared_ptr &client, bool channel_lock, const std::shared_ptr& channel) { - std::deque changed; - - //Server groups - { - auto groups = this->getServerGroups(client->getClientDatabaseId(), client->getType()); - string group_string; - for(const auto& group : groups) - if(group_string.empty()) group_string += to_string(group->group->groupId()); - else group_string += "," + to_string(group->group->groupId()); - if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) { - client->properties()[property::CLIENT_SERVERGROUPS] = group_string; - changed.push_back(property::CLIENT_SERVERGROUPS); - - unique_lock chan_lock(client->channel_lock, defer_lock); - if(channel_lock) - chan_lock.lock(); - - client->cached_server_groups.clear(); - client->cached_server_groups.reserve(groups.size()); - for(const auto& group : groups) - client->cached_server_groups.push_back(group->group->groupId()); - } - - } - - //Channel groups - if(channel){ - shared_ptr group = this->getChannelGroup(client->getClientDatabaseId(), channel, true); - if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != group->channelId) { - client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = group->channelId; - changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID); - } - if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != group->group->groupId()) { - client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = group->group->groupId(); - changed.push_back(property::CLIENT_CHANNEL_GROUP_ID); - - unique_lock chan_lock(client->channel_lock, defer_lock); - if(channel_lock) - chan_lock.lock(); - - client->cached_channel_group = group->group->groupId(); - } - } - - if(!changed.empty()) - client->join_state_id++; /* groups have changed :) */ - - return changed; -} - -void GroupManager::cleanupAssignments(ClientDbId client) { - if(this->root) - this->root->cleanupAssignments(client); - - for(const auto& assignment : this->getAssignedServerGroups(client)) { - if(!assignment->group->is_permanent()) - this->removeServerGroup(client, assignment->group); - } -} - - -void GroupManager::enableCache(const ClientDbId& client_database_id) { - if(this->root) - this->root->enableCache(client_database_id); - - unique_lock cache_lock(this->cacheLock); - /* test if we're already having the client */ - for(auto& entry : this->cachedClients) - if(entry->client_database_id == client_database_id) { - entry->use_count++; - return; /* client already cached, no need to cache client */ - } - - auto entry = std::make_shared(); - entry->client_database_id = client_database_id; - entry->use_count++; - this->cachedClients.push_back(entry); - - lock_guard client_cache_lock(entry->lock); /* lock the client because we're currently loading the cache. */ - cache_lock.unlock(); - - auto res = sql::command(this->sql, "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", client_database_id}) - .query([&](int length, std::string* value, std::string* column) { - shared_ptr group = nullptr; - time_point until; - ChannelId channelId = 0; - - for(int index = 0; index < length; index++){ - try { - if(column[index] == "groupId"){ - group = this->findGroup(stoll(value[index])); - } else if(column[index] == "until"){ - until = time_point() + milliseconds(stoll(value[index])); - } else if(column[index] == "channelId"){ - channelId = stoll(value[index]); - } else { - logError(this->getServerId(), "Unknown column in group assignment query: {}", column[index]); - continue; - } - } catch(std::exception& ex) { - logError(this->getServerId(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", client_database_id, column[index], value[index]); - return 0; - } - } - if(!group) { - return 0; - } - - auto assignment = std::make_shared(); - assignment->group = group; - assignment->until = until; - assignment->parent = &*entry; - assignment->channelId = channelId; - assignment->server = this->getServerId(); - - if(channelId == 0) - entry->server_groups.push_back(assignment); - else - entry->channel_groups[channelId] = assignment; - return 0; - }); -} - -//FIXME: This method till get far more often then it should be. We should add a flag if the group cache is loaded for each std::shared_ptr instance -void GroupManager::disableCache(const ClientDbId& client_database_id) { - if(this->root) { - this->root->disableCache(client_database_id); - } - - lock_guard cache_lock(this->cacheLock); - this->cachedClients.erase(std::remove_if(this->cachedClients.begin(), this->cachedClients.end(), [&](const std::shared_ptr& client) { - if(client->client_database_id != client_database_id) - return false; - - lock_guard client_lock{client->lock}; - return (--client->use_count) == 0; - }), this->cachedClients.end()); -} - -void GroupManager::clearCache() { - if(this->root) this->root->clearCache(); - - lock_guard lock(this->cacheLock); - this->cachedClients.clear(); -} - -bool GroupManager::isClientCached(const ClientDbId& client_database_id) { - return this->resolve_cached_client(client_database_id) == nullptr; -} - -constexpr static auto kGroupMemberListQuery{R"( - SELECT - assignedGroups.cldbid, - clients_server.client_unique_id, - clients_server.client_nickname, - assignedGroups.channelId, - assignedGroups.until - FROM assignedGroups - INNER JOIN clients_server ON - clients_server.client_database_id = assignedGroups.cldbid AND clients_server.server_id = :sid - WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid; -)"}; - -typedef std::vector> ResList; -std::deque GroupManager::listGroupMembers(std::shared_ptr group, bool names) { - if(!isLocalGroup(group)){ - if(this->root) - return this->root->listGroupMembers(group, names); - return {}; - } - - std::deque result{}; - - size_t set_index{0}; - sql::command{this->sql, std::string{kGroupMemberListQuery}, variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}} - .query([&](int length, std::string* values, std::string* names) { - set_index++; - - auto index{0}; - try { - auto& member = result.emplace_back(); - assert(names[index] == "cldbid"); - member.cldbId = std::stoull(values[index++]); - - assert(names[index] == "client_unique_id"); - member.uid = values[index++]; - - assert(names[index] == "client_nickname"); - member.displayName = values[index++]; - - assert(names[index] == "channelId"); - member.channelId = std::stoull(values[index++]); - - assert(names[index] == "until"); - member.until = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoll(values[index++])}; - - assert(index == length); - } catch (std::exception& ex) { - result.pop_back(); - logError(this->getServerId(), "Failed to parse client group assignment for group {}: {}. Set index: {}, Column: {}", - group->groupId(), - ex.what(), - set_index - 1, - index - 1 - ); - return; - } - }); - - return result; -} - -vector> GroupManager::listGroupAssignments(ClientDbId cldbId) { - vector> result; - sql::result res; - - auto cached = resolve_cached_client(cldbId); - if(cached) { - { - lock_guard lock{cached->lock}; - for(const auto &serverGroup : cached->server_groups) - result.push_back(serverGroup); - for(auto& channelGroup : cached->channel_groups) - result.push_back(channelGroup.second); - } - - if(this->root){ - auto append = this->root->listGroupAssignments(cldbId); - for(const auto &elm : append) - result.push_back(elm); - } - - return result; - } - - - res = sql::command(this->sql, "SELECT `groupId`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}) - .query([&](int length, char** value, char** column){ - shared_ptr group = nullptr; - time_point until; - uint64_t channelId = 0; - - for(int index = 0; index < length; index++){ - if(value[index] == nullptr) { - logError(this->getServerId(), string() + "Invalid value at " + column[index]); - continue; - } - if(strcmp(column[index], "groupId") == 0){ - group = this->findGroup(stoll(value[index])); - } else if(strcmp(column[index], "until") == 0){ - until = time_point() + milliseconds(stoll(value[index])); - } else if(strcmp(column[index], "channelId") == 0){ - channelId = stoll(value[index]); - } else cerr << "Invalid column " << column[index] << endl; - } - if(!group) - return 0; - - shared_ptr assignment = std::make_shared(); - assignment->parent = nullptr; - assignment->group = group; - assignment->until = until; - assignment->channelId = channelId; - assignment->server = this->getServerId(); - result.push_back(assignment); - return 0; - }); - (LOG_SQL_CMD)(res); - - if(this->root){ - auto append = this->root->listGroupAssignments(cldbId); - for(const auto &elm : append) - result.push_back(elm); - } - - return result; -} - -std::shared_ptr GroupManager::resolve_cached_client(ClientDbId client_database_id) { - { - lock_guard lock(this->cacheLock); - for(auto& entry : this->cachedClients) - if(entry->client_database_id == client_database_id) - return entry; - } - - return nullptr; -} - -std::vector> GroupManager::getAssignedServerGroups(ClientDbId cldbid) { - auto cached = this->resolve_cached_client(cldbid); - sql::result res; - std::vector> result; - if(this->root) { - auto client_groups = this->root->getAssignedServerGroups(cldbid); - result.insert(result.begin(), client_groups.begin(), client_groups.end()); - } - - if(cached) { - lock_guard cache_lock{cached->lock}; - result.insert(result.end(), cached->server_groups.begin(), cached->server_groups.end()); - return result; - } - - debugMessage(this->getServerId(), "Query client groups for client {} on server {}.", cldbid, this->getServerId()); - res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->getServerId()}, variable{":cldbid", cldbid}).query([&](int length, char** value, char** column){ - shared_ptr group = nullptr; - time_point until; - - for(int index = 0; index < length; index++) { - if(value[index] == nullptr) { - logError(this->getServerId(), string() + "Invalid value at " + column[index]); - continue; - } - if(strcmp(column[index], "groupId") == 0 && value[index] != nullptr){ - group = this->findGroup(stoll(value[index])); - } else if(strcmp(column[index], "until") == 0){ - until = time_point() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index])); - } else cerr << "Invalid column " << column[index] << endl; - } - if(!group) - return 0; - - shared_ptr assignment = std::make_shared(); - assignment->parent = nullptr; - assignment->group = group; - assignment->until = until; - assignment->server = this->getServerId(); - result.push_back(assignment); - return 0; - }); - LOG_SQL_CMD(res); - return result; -} - -std::vector> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) { - auto result = this->getAssignedServerGroups(cldbid); - if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type); - return result; -} - -std::vector> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) { - std::vector> result; - if(type == ClientType::CLIENT_QUERY && this->root) { - auto root = this->root->defaultServerGroupGroupAssignments(client, type); - result.insert(result.begin(), root.begin(), root.end()); - } else if(type == ClientType::CLIENT_MUSIC) { - threads::MutexLock lock(this->cacheLock); - - auto server = this->server.lock(); - auto id = - server ? - server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_or(0) : - serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or(0); - auto group = this->findGroupLocal(id); - if(group) { - result.push_back(std::make_shared(nullptr, this->getServerId(), 0, group, time_point())); - return result; - } - } - result.push_back(std::make_shared(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point())); - return result; -} - -std::shared_ptr GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr& channel, bool assign_default) { - auto cached = resolve_cached_client(cldbId); - if(cached) { - lock_guard cache_lock(cached->lock); - if(cached->channel_groups.count(channel->channelId()) > 0) { - return cached->channel_groups[channel->channelId()]; - } else - return assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : nullptr; - } - - std::shared_ptr result; - auto res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}, variable{":chid", channel->channelId()}).query([&](int length, char** value, char** column){ - shared_ptr group = nullptr; - time_point until; - - for(int index = 0; index < length; index++){ - if(value[index] == nullptr) { - logError(this->getServerId(), string() + "Invalid value at " + column[index]); - continue; - } - if(strcmp(column[index], "groupId") == 0){ - group = this->findGroup(stoll(value[index])); - } else if(strcmp(column[index], "until") == 0){ - until = time_point() + milliseconds(stoll(value[index])); - } else cerr << "Invalid column " << column[index] << endl; - } - if(!group) - return 0; - - shared_ptr assignment = std::make_shared(); - assignment->parent = nullptr; - assignment->group = group; - assignment->until = until; - assignment->server = this->getServerId(); - assignment->channelId = channel->channelId(); - result = std::move(assignment); - return 0; - }); - (LOG_SQL_CMD)(res); - - return !result && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : result; -} - -std::shared_ptr GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr &channel, bool assign_default) { - shared_ptr group; - std::shared_ptr inheritance_channel = channel; - while(inheritance_channel && !group) { - group = this->getChannelGroupExact(cldbId, inheritance_channel, false); - if(!group) { - auto inheritance = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end); - if(inheritance.has_value && inheritance.value == 1) - break; - - inheritance_channel = inheritance_channel->parent(); - } - } - return !group && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : group; -} - -std::shared_ptr GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr &channel) { - return std::make_shared(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point()); -} - -void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr group, time_point until) { - /* - if(!this->isLocalGroup(group)) { - if(this->root) this->root->addServerGroup(cldbId, group, until); - return; - } - if(hasServerGroup(cldbId, group)) return; - */ - - auto cached = resolve_cached_client(cldbId); - if(cached) { - lock_guard cache_lock(cached->lock); - cached->server_groups.push_back(std::make_shared(cached.get(), this->getServerId(), 0, group, until)); - } - - sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", - variable{":sid", this->getServerId()}, - variable{":cldbid", cldbId}, - variable{":gid", group->groupId()}, - variable{":chid", 0}, - variable{":until", time_point_cast(until).time_since_epoch().count()}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); -} - -void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr group) { - if(!this->hasServerGroupAssigned(cldbId, group)) return; - auto cached = resolve_cached_client(cldbId); - if(cached) { - lock_guard cache_lock(cached->lock); - cached->server_groups.erase(std::remove_if(cached->server_groups.begin(), cached->server_groups.end(), [&](const std::shared_ptr& group_assignment) { - return group_assignment->group == group; - }), cached->server_groups.end()); - } - - sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid", - variable{":sid", this->getServerId()}, - variable{":cldbid", cldbId}, - variable{":gid", group->groupId()}, - variable{":chid", 0}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); -} - -void GroupManager::setChannelGroup(ClientDbId cldbId, std::shared_ptr group, std::shared_ptr channel, time_point until) { - auto old_group = getChannelGroupExact(cldbId, channel, false); - if(old_group) { - if(old_group->group == group) return; - } else if(!group) return; - - auto default_group = !group || group == this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); - auto cached = resolve_cached_client(cldbId); - if(cached) { - lock_guard cache_lock(cached->lock); - if(default_group) - cached->channel_groups.erase(channel->channelId()); - else - cached->channel_groups[channel->channelId()] = std::make_shared(cached.get(), this->getServerId(), channel->channelId(), group, until); - } - - sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", - variable{":sid", this->getServerId()}, - variable{":cldbid", cldbId}, - variable{":chid", channel->channelId()}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); - - if(!default_group) { - sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", - variable{":sid", this->getServerId()}, - variable{":cldbid", cldbId}, - variable{":gid", group->groupId()}, - variable{":chid", channel->channelId()}, - variable{":until", time_point_cast(until).time_since_epoch().count()}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); - } -} \ No newline at end of file diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index b8e5eb2..0711dca 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -921,7 +921,7 @@ bool InstanceHandler::validate_default_groups() { auto& assignments = this->group_manager_->assignments(); auto client_assignments = assignments.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, this->globalServerAdmin->getClientDatabaseId()); if(client_assignments.empty()) { - assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id()); + assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id(), false); } } } diff --git a/server/src/InstanceHandlerPermissions.cpp b/server/src/InstanceHandlerPermissions.cpp index cdbe902..5502ac1 100644 --- a/server/src/InstanceHandlerPermissions.cpp +++ b/server/src/InstanceHandlerPermissions.cpp @@ -4,6 +4,7 @@ #include "./InstanceHandler.h" #include "./groups/GroupManager.h" +#include "./PermissionCalculator.h" using namespace ts; using namespace ts::server; @@ -24,53 +25,11 @@ permission::v2::PermissionFlaggedValue InstancePermissionHelper::calculate_permi std::vector > InstancePermissionHelper::calculate_permissions( const std::deque &permissions, ClientDbId cldbid, - ClientType /* type */, - ChannelId /* channel */, + ClientType type, + ChannelId channel, bool granted, std::shared_ptr /* cache */ ) const { - std::vector> result{}; - - /* TODO: Use same algorithm as on normal servers */ - std::vector> assigned_groups{}; - { - using groups::GroupAssignmentCalculateMode; - using groups::GroupCalculateMode; - - auto group_manager = this->instance->group_manager(); - auto& assignment_manager = group_manager->assignments(); - - auto assignments = assignment_manager.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, cldbid); - if(assignments.empty()) { - assignments.push_back(this->instance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save()); - } - - assigned_groups.reserve(assignments.size()); - for(const auto& group_id : assignments) { - auto group = group_manager->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id); - if(!group) { - continue; - } - - assigned_groups.push_back(std::move(group)); - } - } - - for(const auto& permission : permissions) { - permission::v2::PermissionFlaggedValue value{0, false}; - - for(const auto &gr : assigned_groups){ - auto group_permissions = gr->permissions(); - auto flagged_permissions = granted ? group_permissions->permission_granted_flagged(permission) : group_permissions->permission_value_flagged(permission); - if(flagged_permissions.has_value) { - if(!value.has_value || flagged_permissions.value > value.value || flagged_permissions.value == -1) { - value = flagged_permissions; - } - } - } - - result.emplace_back(permission, value); - } - - return result; + ClientPermissionCalculator calculator{nullptr, cldbid, type, channel}; + return calculator.calculate_permissions(permissions, granted); } \ No newline at end of file diff --git a/server/src/InstanceHandlerSetup.cpp b/server/src/InstanceHandlerSetup.cpp index ad38650..4d53915 100644 --- a/server/src/InstanceHandlerSetup.cpp +++ b/server/src/InstanceHandlerSetup.cpp @@ -1,11 +1,10 @@ -#include #include #include -#include #include #include "InstanceHandler.h" #include "src/client/InternalClient.h" #include "src/server/QueryServer.h" +#include "./groups/GroupManager.h" using namespace std; using namespace std::chrono; @@ -23,9 +22,9 @@ struct GroupInfo { */ int target; std::deque properties; - string name; + std::string name; /* permission type, value, granted, skip, negate */ - deque> permissions; + std::deque> permissions; }; /* TODO may use a transaction here? */ @@ -123,14 +122,41 @@ bool InstanceHandler::setupDefaultGroups() { for(const auto& info : groups) { debugMessage(LOG_INSTANCE, "Creating default group {} with type {}", info->name, to_string(info->target)); //Query groups - auto group = this->group_manager_->createGroup( - info->target != 2 ? GroupTarget::GROUPTARGET_SERVER : GroupTarget::GROUPTARGET_CHANNEL, - info->target == 0 ? GroupType::GROUP_TYPE_QUERY : GroupType::GROUP_TYPE_TEMPLATE, - info->name - ); + + std::shared_ptr created_group{}; + groups::GroupCreateResult create_result{}; + + if(info->target == 2) { + std::shared_ptr c_group{}; + create_result = serverInstance->group_manager()->channel_groups()->create_group(groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, c_group); + created_group = c_group; + } else { + std::shared_ptr s_group{}; + create_result = serverInstance->group_manager()->channel_groups()->create_group(info->target == 0 ? groups::GroupType::GROUP_TYPE_QUERY : groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, s_group); + created_group = s_group; + } + + switch(create_result) { + case groups::GroupCreateResult::SUCCESS: + break; + + case groups::GroupCreateResult::DATABASE_ERROR: + logCritical(LOG_INSTANCE, "Failed to insert template group {} (Database error)", info->name); + return false; + + case groups::GroupCreateResult::NAME_TOO_LONG: + case groups::GroupCreateResult::NAME_ALREADY_IN_USED: + case groups::GroupCreateResult::NAME_TOO_SHORT: + logCritical(LOG_INSTANCE, "Failed to insert template group {} (Name issue)", info->name); + return false; + + default: + logCritical(LOG_INSTANCE, "Failed to insert template group {} (Unkown error)", info->name); + return false; + } for(auto perm : info->permissions) { - group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, get<1>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<2>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<3>(perm), get<4>(perm)); + created_group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, get<1>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<2>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<3>(perm), get<4>(perm)); } for(const auto& property : info->properties) { @@ -138,7 +164,7 @@ bool InstanceHandler::setupDefaultGroups() { if(prop.is_undefined()) { logCritical(LOG_INSTANCE, "Invalid template property name: " + property); } else { - this->properties()[prop] = group->groupId(); + this->properties()[prop] = created_group->group_id(); } } } diff --git a/server/src/PermissionCalculator.cpp b/server/src/PermissionCalculator.cpp index 4ff66e6..f87ba01 100644 --- a/server/src/PermissionCalculator.cpp +++ b/server/src/PermissionCalculator.cpp @@ -19,10 +19,18 @@ ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, Chann auto server = client->getServer(); if(server && channel_id > 0) { - std::shared_lock channel_lock{server->get_channel_tree_lock()}; - auto channel = server->getChannelTree()->findChannel(channel_id); - channel_lock.unlock(); + std::shared_ptr channel{}; + try { + std::shared_lock channel_lock{server->get_channel_tree_lock()}; + channel = server->getChannelTree()->findChannel(channel_id); + } catch (std::system_error& e) { + if(e.code() != std::errc::resource_deadlock_would_occur) { + throw; + } + /* tree already write locked, no need to lock it again */ + channel = server->getChannelTree()->findChannel(channel_id); + } if(channel) { this->channel_permissions = channel->permissions(); } @@ -56,10 +64,18 @@ ClientPermissionCalculator::ClientPermissionCalculator( this->group_manager_ = server->group_manager(); this->default_channel_group = [server]{ return server->default_channel_group(); }; - std::shared_lock channel_lock{server->get_channel_tree_lock()}; - auto channel = server->getChannelTree()->findChannel(channel_id); - channel_lock.unlock(); + std::shared_ptr channel{}; + try { + std::shared_lock channel_lock{server->get_channel_tree_lock()}; + channel = server->getChannelTree()->findChannel(channel_id); + } catch (std::system_error& e) { + if(e.code() != std::errc::resource_deadlock_would_occur) { + throw; + } + /* tree already write locked, no need to lock it again */ + channel = server->getChannelTree()->findChannel(channel_id); + } if(channel) { this->channel_permissions = channel->permissions(); } diff --git a/server/src/ServerManagerSnapshotDeploy.cpp b/server/src/ServerManagerSnapshotDeploy.cpp index 212c2e8..a9accf3 100644 --- a/server/src/ServerManagerSnapshotDeploy.cpp +++ b/server/src/ServerManagerSnapshotDeploy.cpp @@ -6,7 +6,7 @@ #include "VirtualServerManager.h" #include "src/server/VoiceServer.h" #include "InstanceHandler.h" -#include "InstanceHandler.h" +#include "./groups/GroupManager.h" //TODO: When using the new command builder make sure you're using a std::deque as the underlying bulk type! @@ -979,10 +979,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptrgroup_manager()->availableServerGroups(false)) { - cmd[index]["id"] = group->groupId(); - cmd[index]["name"] = group->name(); - if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break; + auto server_groups = server->group_manager()->server_groups(); + for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) { + cmd[index]["id"] = group->group_id(); + cmd[index]["name"] = group->display_name(); + if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) { + break; + } cmd[index++]["end_group"] = ""; } cmd[index++]["end_groups"] = ""; @@ -1001,10 +1004,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptrgroup_manager()->availableChannelGroups(false)) { - cmd[index]["id"] = group->groupId(); - cmd[index]["name"] = group->name(); - if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break; + auto server_groups = server->group_manager()->channel_groups(); + for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) { + cmd[index]["id"] = group->group_id(); + cmd[index]["name"] = group->display_name(); + if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) { + break; + } cmd[index++]["end_group"] = ""; } cmd[index++]["end_groups"] = ""; diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 109a569..f5ec51a 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -550,7 +550,8 @@ 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(); - this->group_manager()->assignments().cleanup_channel_temporary_assignment(target->getClientDatabaseId(), s_source_channel->channelId()); + this->group_manager()->assignments().cleanup_temporary_channel_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; diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index 5f9c62c..14c34f2 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -976,8 +976,8 @@ bool VirtualServer::resetPermissions(std::string& new_permission_token) { 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()->server_groups()->reset_groups(server_group_mapping); + this->group_manager()->channel_groups()->reset_groups(channel_group_mapping); this->group_manager()->assignments().reset_all(); } @@ -1041,6 +1041,8 @@ bool VirtualServer::resetPermissions(std::string& new_permission_token) { } void VirtualServer::ensureValidDefaultGroups() { + /* TODO: FIXME: Impl! */ +#if 0 auto default_server_group = this->group_manager()->defaultGroup(GROUPTARGET_SERVER, true); if(!default_server_group) { logError(this->serverId, "Missing server's default server group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].value()); @@ -1077,6 +1079,7 @@ void VirtualServer::ensureValidDefaultGroups() { logError(this->serverId, "Using {} ({}) instead!", admin_channel_group->groupId(), admin_channel_group->name()); this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = admin_channel_group->groupId(); } +#endif } void VirtualServer::send_text_message(const std::shared_ptr &channel, const std::shared_ptr &sender, const std::string &message) { diff --git a/server/src/channel/ServerChannel.cpp b/server/src/channel/ServerChannel.cpp index a39863b..b5f0828 100644 --- a/server/src/channel/ServerChannel.cpp +++ b/server/src/channel/ServerChannel.cpp @@ -7,6 +7,7 @@ #include "src/client/ConnectedClient.h" #include "src/InstanceHandler.h" #include "../manager/ConversationManager.h" +#include "../groups/GroupManager.h" using namespace std; using namespace ts; @@ -575,11 +576,11 @@ void ServerChannelTree::on_channel_entry_deleted(const shared_ptr auto server = this->server_ref.lock(); if(server) { - server->group_manager()->handleChannelDeleted(channel->channelId()); + server->group_manager()->assignments().handle_channel_deleted(channel->channelId()); server->conversation_manager()->delete_conversation(channel->channelId()); server->rtc_server().destroy_channel(server_channel->rtc_channel_id); } else { - serverInstance->group_manager()->handleChannelDeleted(channel->channelId()); + serverInstance->group_manager()->assignments().handle_channel_deleted(channel->channelId()); } diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 63b01b6..5e90d2b 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -61,7 +61,8 @@ void ConnectedClient::initialize_weak_reference(const std::shared_ptrtask_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 changed{false}; + self->update_displayed_client_groups(changed, changed); } }}; } @@ -487,6 +488,15 @@ bool ConnectedClient::notifyClientLeftView( invoker = this->server->serverRoot; } break; + + case ViewReasonId::VREASON_USER_ACTION: + case ViewReasonId::VREASON_SYSTEM: + case ViewReasonId::VREASON_TIMEOUT: + case ViewReasonId::VREASON_SERVER_STOPPED: + case ViewReasonId::VREASON_SERVER_LEFT: + case ViewReasonId::VREASON_CHANNEL_UPDATED: + case ViewReasonId::VREASON_EDITED: + case ViewReasonId::VREASON_SERVER_SHUTDOWN: default: break; } @@ -818,7 +828,7 @@ void ConnectedClient::sendServerInit() { command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].value(); command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].value(); - if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE){ + if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE) { if(serverInstance->getVoiceServerManager()->usedSlots() <= 32) command["lt"] = LicenseType::LICENSE_NONE; else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512) @@ -1037,19 +1047,28 @@ void ConnectedClient::update_displayed_client_groups(bool& server_groups_changed if(!server_group_assignments.empty()) { server_group_assignments = server_group_assignments.substr(1); } + + std::unique_lock view_lock{this->channel_lock}; + this->cached_server_groups = server_groups; } { 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 if(ref_server) { + channel_group_id = ref_server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or(0); + channel_inherit_id = 0; } else { channel_group_id = 0; channel_inherit_id = 0; } + + this->cached_channel_group = channel_group_id; } server_groups_changed = false; @@ -1174,7 +1193,7 @@ void ConnectedClient::useToken(token::TokenId token_id) { } if(action.type == token::ActionType::AddServerGroup) { - auto result = this->server->group_manager()->assignments().add_server_group(this->getClientDatabaseId(), group->group_id()); + auto result = this->server->group_manager()->assignments().add_server_group(this->getClientDatabaseId(), group->group_id(), !group->is_permanent()); switch(result) { case GroupAssignmentResult::SUCCESS: debugMessage(this->getServerId(), "{} Executing token action add server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1); @@ -1260,15 +1279,16 @@ void ConnectedClient::useToken(token::TokenId token_id) { } if(tree_registered && server_groups_changed) { - for(const auto &viewer : this->server->getClients()) { - if(viewer->isClientVisible(this->ref(), true)) { - for(const auto& group : added_server_groups) { - viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), group); - } - - for(const auto& group : removed_server_groups) { - viewer->notifyServerGroupClientRemove(this->server->serverRoot, this->ref(), group); - } + for(const auto& group : added_server_groups) { + std::optional notify{}; + for(const auto &viewer : this->server->getClients()) { + viewer->notifyServerGroupClientAdd(notify, this->server->serverRoot, this->ref(), group->group_id()); + } + } + for(const auto& group : added_server_groups) { + std::optional notify{}; + for(const auto &viewer : this->server->getClients()) { + viewer->notifyServerGroupClientRemove(notify, this->server->serverRoot, this->ref(), group->group_id()); } } } diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 65472a7..2eac84f 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -120,10 +120,11 @@ namespace ts { std::deque> unsubscribeChannel(const std::deque>& target, bool lock_channel); bool isClientVisible(const std::shared_ptr&, bool /* lock channel lock */); - inline std::deque> getVisibleClients(bool lock_channel){ + inline std::deque> getVisibleClients(bool lock_channel) { std::shared_lock lock(this->channel_lock, std::defer_lock); - if(lock_channel) + if(lock_channel) { lock.lock(); + } return this->visibleClients; } @@ -162,20 +163,44 @@ namespace ts { inline void sendChannelMessage(const std::shared_ptr& sender, const std::string& textMessage){ this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, this->currentChannel ? this->currentChannel->channelId() : 0, 0, std::chrono::system_clock::now(), textMessage); } - //Group Client Groups - virtual bool notifyServerGroupClientAdd(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &group); - virtual bool notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group); - /* invalid client id causes error: invalid clientID */ - /* invalid channel id causes error: invalid channelID */ - /* an invalid channel or not a client's channel causes: invalid channelID */ + + /** + * Notify the client that a client has received a new server group. + * If the target client isn't visible no notify will be send. + * + * Note: + * 1. This method will lock the channel tree in shared mode! + * 2. For TS3 clients if the client isn't in view the client will disconnect. + */ + virtual bool notifyServerGroupClientAdd(std::optional& /* generated notify */, const std::shared_ptr &/* invoker */, const std::shared_ptr &/* target client */, const GroupId& /* group id */); + + /** + * Notify the client that a client has removed a server group. + * If the target client isn't visible no notify will be send. + * + * Note: + * 1. This method will lock the channel tree in shared mode! + * 2. For TS3 clients if the client isn't in view the client will disconnect. + */ + virtual bool notifyServerGroupClientRemove(std::optional& /* generated notify */, const std::shared_ptr &/* invoker */, const std::shared_ptr &/* target client */, const GroupId& /* group id */); + + /** + * Notify that a client has received a new channel group. + * If the target client isn't visible no notify will be send. + * + * Note: + * 1. This method will lock the channel tree in shared mode! + * 2. For TS3 clients if the client isn't in view the client will disconnect. + */ virtual bool notifyClientChannelGroupChanged( - const std::shared_ptr& invoker, - const std::shared_ptr& client, - const std::shared_ptr& /*channel*/, - const std::shared_ptr& /*inherited channel*/, - const std::shared_ptr& group, - bool lock_channel_tree /* server channel tree AND client tree must be at least read locked! */ + std::optional& /* generated notify */, + const std::shared_ptr& /* invoker */, + const std::shared_ptr& /* target client */, + const ChannelId& /* channel */, + const ChannelId& /* inherited channel */, + const GroupId& /* group id */ ); + //Group channel virtual bool notifyChannelMoved(const std::shared_ptr &channel, ChannelId order, const std::shared_ptr &invoker); virtual bool notifyChannelDescriptionChanged(std::shared_ptr channel); @@ -306,6 +331,8 @@ namespace ts { bool update_client_needed_permissions(); /** + * Note: `server_groups_changed` and `channel_group_changed` could be equal. + * If so it will be true if any changes have been made. * Attention: This method should never be called directly! * Use `task_update_displayed_groups` instead to schedule an update. */ @@ -696,6 +723,13 @@ namespace ts { return std::string(notify); return ""; } + + command_result handleCommandGroupAdd(Command&, GroupTarget); + command_result handleCommandGroupCopy(Command&, GroupTarget); + command_result handleCommandGroupRename(Command&, GroupTarget); + command_result handleCommandGroupDel(Command&, GroupTarget); + + command_result executeGroupPermissionEdit(Command&, const std::vector>& /* groups */, const std::shared_ptr& /* target server */, permission::v2::PermissionUpdateType /* mode */); }; template diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index da2d8b0..88d7637 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -70,9 +70,9 @@ inline void build_group_notify(ts::command_builder& notify, bool is_channel_grou 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("savedb", group->is_permanent()); bulk.put_unchecked("namemode", (uint8_t) group->name_mode()); - bulk.put_unchecked("iconid", group->icon_id()); + bulk.put_unchecked("iconid", (int32_t) group->icon_id()); auto modify_power = group->permissions()->permission_value_flagged(permission_modify); auto add_power = group->permissions()->permission_value_flagged(permission_add); @@ -88,7 +88,7 @@ bool ConnectedClient::notifyServerGroupList(std::optional & 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); + auto available_groups = group_manager->server_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); build_group_notify(generated_notify.emplace(as_notify ? "notifyservergrouplist" : ""), false, available_groups); } @@ -225,57 +225,95 @@ bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, const shared_ptr &client, const shared_ptr &group) { - Command cmd("notifyservergroupclientadded"); - INVOKER(cmd, invoker); - - cmd["sgid"] = group->groupId(); - cmd["clid"] = client->getClientId(); - cmd["name"] = client->getDisplayName(); - cmd["cluid"] = client->getUid(); - - this->sendCommand(cmd); - return true; -} - -bool ConnectedClient::notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) { - Command cmd("notifyservergroupclientdeleted"); - INVOKER(cmd, invoker); - - cmd["sgid"] = group->groupId(); - cmd["clid"] = client->getClientId(); - cmd["name"] = client->getDisplayName(); - cmd["cluid"] = client->getUid(); - - this->sendCommand(cmd); - return true; -} - -bool ConnectedClient::notifyClientChannelGroupChanged( +bool ConnectedClient::notifyServerGroupClientAdd( + std::optional& notify, const std::shared_ptr &invoker, - const std::shared_ptr &client, - const std::shared_ptr& channel, - const std::shared_ptr& inherited, - const std::shared_ptr& group, - bool lock_channel_tree) { - assert(!lock_channel_tree); /* not supported */ + const std::shared_ptr &target_client, + const GroupId& group_id) { - if(!this->isClientVisible(client, false) && client != this) return false; - if(client->getChannel() != channel) return false; + /* Deny any client moves 'till we've send the notify */ + std::shared_lock channel_tree_lock{}; + if(this->server) { + channel_tree_lock = std::shared_lock{this->server->channel_tree_lock}; + } - assert(client); - assert(channel); - assert(inherited); - assert(group); - Command cmd("notifyclientchannelgroupchanged"); - INVOKER(cmd, invoker); + if(!this->isClientVisible(target_client, true)) { + return false; + } - cmd["cgid"] = group->groupId(); - cmd["clid"] = client->getClientId(); - cmd["cid"] = channel->channelId(); - cmd["cgi"] = inherited ? inherited->channelId() : channel->channelId(); //The inherited channel + if(!notify.has_value()) { + notify.emplace("notifyservergroupclientadded"); + INVOKER_NEW((*notify), invoker); - this->sendCommand(cmd); + notify->put_unchecked(0, "sgid", group_id); + notify->put_unchecked(0, "clid", target_client->getClientId()); + notify->put_unchecked(0, "name", target_client->getDisplayName()); + notify->put_unchecked(0, "cluid", target_client->getUid()); + } + + this->sendCommand(*notify); + return true; +} + +bool ConnectedClient::notifyServerGroupClientRemove( + std::optional& notify, + const std::shared_ptr &invoker, + const std::shared_ptr &target_client, + const GroupId& group_id) { + + /* Deny any client moves 'till we've send the notify */ + std::shared_lock channel_tree_lock{}; + if(this->server) { + channel_tree_lock = std::shared_lock{this->server->channel_tree_lock}; + } + + if(!this->isClientVisible(target_client, true)) { + return false; + } + + if(!notify.has_value()) { + notify.emplace("notifyservergroupclientdeleted"); + INVOKER_NEW((*notify), invoker); + + notify->put_unchecked(0, "sgid", group_id); + notify->put_unchecked(0, "clid", target_client->getClientId()); + notify->put_unchecked(0, "name", target_client->getDisplayName()); + notify->put_unchecked(0, "cluid", target_client->getUid()); + } + + this->sendCommand(*notify); + return true; +} + +bool ConnectedClient::notifyClientChannelGroupChanged(std::optional ¬ify, + const std::shared_ptr &invoker, + const std::shared_ptr &target_client, + const ChannelId &channel_id, + const ChannelId &inherited_channel_id, + const GroupId &group_id) { + /* Deny any client moves 'till we've send the notify */ + std::shared_lock channel_tree_lock{}; + if(this->server) { + channel_tree_lock = std::shared_lock{this->server->channel_tree_lock}; + } + + /* No need to check if the channel is visible since if this is the case the client would not be visible as well. */ + if(!this->isClientVisible(target_client, true)) { + return false; + } + + if(!notify.has_value()) { + notify.emplace("notifyclientchannelgroupchanged"); + INVOKER_NEW((*notify), invoker); + + notify->put_unchecked(0, "cgid", group_id); + notify->put_unchecked(0, "clid", target_client->getClientId()); + notify->put_unchecked(0, "name", target_client->getDisplayName()); + notify->put_unchecked(0, "cid", channel_id); + notify->put_unchecked(0, "cgi", inherited_channel_id == 0 ? channel_id : inherited_channel_id); + } + + this->sendCommand(*notify); return true; } diff --git a/server/src/client/DataClient.cpp b/server/src/client/DataClient.cpp index f61e0a6..c76e142 100644 --- a/server/src/client/DataClient.cpp +++ b/server/src/client/DataClient.cpp @@ -8,6 +8,8 @@ #include "../groups/GroupManager.h" #include "../groups/GroupAssignmentManager.h" +#include "../PermissionCalculator.h" + using namespace std; using namespace ts; using namespace ts::server; @@ -138,6 +140,12 @@ bool DataClient::loadDataForCurrentServer() { this->properties()[property::CLIENT_UNREAD_MESSAGES] = ref_server->letters->unread_letter_count(this->getUid()); } + if(this->server) { + this->temporary_assignments_lock = server->group_manager()->assignments().create_tmp_assignment_lock(this->getClientDatabaseId()); + } else { + this->temporary_assignments_lock = serverInstance->group_manager()->assignments().create_tmp_assignment_lock(this->getClientDatabaseId()); + } + return true; } @@ -145,40 +153,16 @@ std::vector &permissions, ChannelId channel, bool granted, - std::shared_ptr cache) { - if(permissions.empty()) { - return {}; - } + std::shared_ptr) { - if(!cache) { - cache = std::make_shared(); - } - - if(!cache->client_permissions) { - /* so we don't have to load that shit later */ - cache->client_permissions = this->clientPermissions; - } - - if(channel == -1) { - auto current_channel = this->currentChannel; - channel = current_channel ? current_channel->channelId() : 0; - } - - auto ref_server = this->server; - if(ref_server) { - return ref_server->calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache); - } else { - return serverInstance->permission_helper().calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache); - } + ts::server::ClientPermissionCalculator calculator{this, channel}; + return calculator.calculate_permissions(permissions, granted); } permission::v2::PermissionFlaggedValue DataClient::calculate_permission( - permission::PermissionType permission, ChannelId channel, bool granted, std::shared_ptr cache) { - auto result = this->calculate_permissions({permission}, channel, granted, cache); - if(result.empty()) { - return {0, false}; - } - return result.back().second; + permission::PermissionType permission, ChannelId channel, bool granted, std::shared_ptr) { + ts::server::ClientPermissionCalculator calculator{this, channel}; + return calculator.calculate_permission(permission, granted); } std::vector> DataClient::assignedServerGroups() { @@ -208,17 +192,16 @@ std::vector> DataClient::assignedServerGrou return result; } -std::shared_ptr DataClient::assignedChannelGroup(const std::shared_ptr &channel) { +std::shared_ptr DataClient::assignedChannelGroup(std::shared_ptr &channel) { auto ref_server = this->server; assert(channel); if(!channel || !ref_server) { return nullptr; } - std::shared_ptr inherited_channel{channel}; - + std::shared_ptr original_channel{channel}; auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); - auto assigned_group_id = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), inherited_channel); + auto assigned_group_id = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), channel); std::shared_ptr result{}; if(assigned_group_id.has_value()) { @@ -227,6 +210,7 @@ std::shared_ptr DataClient::assignedChannelGroup(const std if(!result) { result = ref_server->default_channel_group(); + channel = original_channel; } assert(result); @@ -252,7 +236,8 @@ bool DataClient::channelGroupAssigned(const shared_ptr &gr return false; } - return this->assignedChannelGroup(channel) == group; + std::shared_ptr interited_channel{channel}; + return this->assignedChannelGroup(interited_channel) == group; } std::string DataClient::getAvatarId() { diff --git a/server/src/client/DataClient.h b/server/src/client/DataClient.h index 9b1d569..938f65e 100644 --- a/server/src/client/DataClient.h +++ b/server/src/client/DataClient.h @@ -22,6 +22,7 @@ namespace ts { namespace groups { class ServerGroup; class ChannelGroup; + typedef void TemporaryAssignmentsLock; } class DataClient { @@ -68,7 +69,7 @@ namespace ts { ); virtual std::vector> assignedServerGroups(); - virtual std::shared_ptr assignedChannelGroup(const std::shared_ptr &); + virtual std::shared_ptr assignedChannelGroup(std::shared_ptr &); virtual bool serverGroupAssigned(const std::shared_ptr &); virtual bool channelGroupAssigned(const std::shared_ptr &, const std::shared_ptr &); @@ -106,6 +107,7 @@ namespace ts { std::shared_ptr _properties; std::shared_ptr currentChannel = nullptr; + std::shared_ptr temporary_assignments_lock{}; }; } } \ No newline at end of file diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 57b97ce..665042c 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -16,6 +16,7 @@ #include "../manager/ActionLogger.h" #include "./voice/VoiceClient.h" #include "../rtc/imports.h" +#include "../groups/GroupManager.h" using namespace std::chrono; using namespace ts; @@ -25,7 +26,7 @@ using namespace ts::protocol; //#define PKT_LOG_VOICE //#define PKT_LOG_WHISPER -SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr &b) : ConnectedClient(a, b), whisper_handler_{this} { +SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr &b) : ConnectedClient{a, b}, whisper_handler_{this} { speak_begin = std::chrono::system_clock::now(); speak_last_packet = std::chrono::system_clock::now(); }; @@ -122,8 +123,6 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { } TIMING_STEP(timings, "db assign "); - this->server->group_manager()->enableCache(this->getClientDatabaseId()); - TIMING_STEP(timings, "gr cache "); const static vector available_parameters = { "client_nickname", @@ -437,10 +436,6 @@ void SpeakingClient::processJoin() { } TIMING_STEP(timings, "server reg "); - ref_server->group_manager()->cleanupAssignments(this->getClientDatabaseId()); - TIMING_STEP(timings, "grp cleanup"); - ref_server->group_manager()->update_server_group_property(this->ref(), true, nullptr); - TIMING_STEP(timings, "grp apply "); this->properties()[property::CLIENT_COUNTRY] = config::geo::countryFlag; if(geoloc::provider) { @@ -574,7 +569,6 @@ void SpeakingClient::processLeave() { unique_lock server_channel_lock(this->server->channel_tree_lock); server->unregisterClient(ownLock, "disconnected", server_channel_lock); /* already moves client to void if needed */ } - server->group_manager()->disableCache(ownLock->getClientDatabaseId()); server->music_manager_->cleanup_client_bots(this->getClientDatabaseId()); //ref_server = nullptr; Removed caused nullptr exceptions } diff --git a/server/src/client/command_handler/bulk_parsers.h b/server/src/client/command_handler/bulk_parsers.h index 2b3c818..20cff87 100644 --- a/server/src/client/command_handler/bulk_parsers.h +++ b/server/src/client/command_handler/bulk_parsers.h @@ -9,11 +9,9 @@ #include "../../manager/ActionLogger.h" namespace ts::command::bulk_parser { - template class PermissionBulkParser { - friend class PermissionBulkParser; public: - explicit PermissionBulkParser(ts::ParameterBulk& bulk) { + explicit PermissionBulkParser(ts::ParameterBulk& bulk, bool parse_value) { if(bulk.has("permid")) { auto type = bulk["permid"].as(); if ((type & PERM_ID_GRANT) != 0) { @@ -35,7 +33,7 @@ namespace ts::command::bulk_parser { return; } - if(kParseValue) { + if(parse_value) { if(!bulk.has("permvalue")) { this->error_.reset(ts::command_result{error::parameter_missing, "permvalue"}); return; @@ -180,7 +178,6 @@ namespace ts::command::bulk_parser { #endif }; - template class PermissionBulksParser { public: PermissionBulksParser(const PermissionBulksParser&) = delete; @@ -220,7 +217,7 @@ namespace ts::command::bulk_parser { }; struct FilteredPermissionListIterable { - typedef typename std::vector>::const_iterator const_iterator; + typedef typename std::vector::const_iterator const_iterator; public: FilteredPermissionListIterable(const_iterator begin, const_iterator end) noexcept : begin_{begin}, end_{end} {} @@ -236,10 +233,11 @@ namespace ts::command::bulk_parser { const_iterator end_; }; - explicit PermissionBulksParser(ts::Command& command) { + explicit PermissionBulksParser(ts::Command& command, bool parse_value) : parse_value{parse_value} { this->permissions_.reserve(command.bulkCount()); - for(size_t index{0}; index < command.bulkCount(); index++) - this->permissions_.emplace_back(command[index]); + for(size_t index{0}; index < command.bulkCount(); index++) { + this->permissions_.emplace_back(command[index], parse_value); + } } [[nodiscard]] inline bool validate(const std::shared_ptr& issuer, ChannelId channel_id) { @@ -247,19 +245,24 @@ namespace ts::command::bulk_parser { if(!ignore_granted_values) { auto max_value = issuer->calculate_permission(permission::i_permission_modify_power, channel_id, false); if(!max_value.has_value) { - for(PermissionBulkParser& permission : this->permissions_) { - if(permission.has_error()) continue; + for(PermissionBulkParser& permission : this->permissions_) { + if(permission.has_error()) { + continue; + } permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power}); } + return false; } for(size_t index{0}; index < this->permissions_.size(); index++) { - PermissionBulkParser& permission = this->permissions_[index]; - if(permission.has_error()) continue; + PermissionBulkParser& permission = this->permissions_[index]; + if(permission.has_error()) { + continue; + } - if(kParseValue && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) { + if(this->parse_value && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) { permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power}); continue; } @@ -281,13 +284,15 @@ namespace ts::command::bulk_parser { assert(!std::exchange(this->result_created_, true)); ts::command_result_bulk result{}; - for(auto& permission : this->permissions_) + for(auto& permission : this->permissions_) { result.insert_result(std::forward(permission.release_error())); + } return ts::command_result{std::move(result)}; } private: - std::vector> permissions_{}; + bool parse_value; + std::vector permissions_{}; #ifndef NDEBUG bool result_created_{false}; #endif diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index a63ae0f..97d5d88 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -134,418 +134,19 @@ command_result ConnectedClient::handleCommandChannelUnsubscribeAll(Command &cmd) } command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { - CMD_RESET_IDLE; - 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(); - - log::GroupType log_group_type; - 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; - - 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"}; - } - - 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; - - case groups::GroupCreateResult::NAME_TOO_SHORT: - case groups::GroupCreateResult::NAME_TOO_LONG: - return command_result{error::parameter_invalid, "name"}; - - 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->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) { - this->server->enqueue_notify_channel_group_list(); - } - } else { - return command_result{error::group_invalid_id}; - } - return command_result{error::ok}; + return this->handleCommandGroupAdd(cmd, GroupTarget::GROUPTARGET_CHANNEL); } -//name=Channel\sAdmin scgid=5 tcgid=4 type=1 command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { - CMD_RESET_IDLE; - CMD_CHK_AND_INC_FLOOD_POINTS(5); - - auto ref_server = this->server; - - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); - - auto source_group_id = cmd["scgid"].as(); - auto source_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, source_group_id); - - if (!source_group) { - return command_result{error::group_invalid_id, "invalid source group id"}; - } - - const auto group_type_modifiable = [&](groups::GroupType type) { - switch (type) { - 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 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 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; - } - return 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_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"}; - } - - 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}; - } - - { - 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->group_type()) { - case groups::GroupType::GROUP_TYPE_QUERY: - log_group_type = log::GroupType::QUERY; - break; - - case groups::GroupType::GROUP_TYPE_TEMPLATE: - log_group_type = log::GroupType::TEMPLATE; - break; - - case groups::GroupType::GROUP_TYPE_NORMAL: - case groups::GroupType::GROUP_TYPE_UNKNOWN: - default: - log_group_type = log::GroupType::NORMAL; - break; - } - - 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->channel_groups()->find_group(groups::GroupCalculateMode::LOCAL, target_group_id); - } else { - //Copy a new group - auto target_type = cmd["type"].as(); - - log::GroupType log_group_type; - switch (target_type) { - case groups::GroupType::GROUP_TYPE_QUERY: - log_group_type = log::GroupType::QUERY; - break; - - case groups::GroupType::GROUP_TYPE_TEMPLATE: - log_group_type = log::GroupType::TEMPLATE; - break; - - 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"}; - } - - { - 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!"}; - } - - 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; - - 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}; - } - - 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; - } - - 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}; + return this->handleCommandGroupCopy(cmd, GroupTarget::GROUPTARGET_CHANNEL); } 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(); - 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->group_type(); - log::GroupType log_group_type; - 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 == 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->display_name(); - auto result = group_manager->channel_groups()->rename_group(channel_group->group_id(), cmd["name"].string()); - switch(result) { - case groups::GroupRenameResult::SUCCESS: - break; - - 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}; + return this->handleCommandGroupRename(cmd, GroupTarget::GROUPTARGET_CHANNEL); } command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { - CMD_RESET_IDLE; - CMD_CHK_AND_INC_FLOOD_POINTS(5); - - 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->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->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->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}; + return this->handleCommandGroupDel(cmd, GroupTarget::GROUPTARGET_CHANNEL); } command_result ConnectedClient::handleCommandChannelGroupList(Command &) { @@ -614,95 +215,39 @@ command_result ConnectedClient::handleCommandChannelGroupPermList(Command &cmd) command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); + auto group_id = cmd["cgid"].as(); + + std::shared_ptr owning_manager{}; 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); + auto group = group_manager->channel_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if (!pparser.validate(this->ref(), 0)) { - return pparser.build_command_result(); + if(!group) { + return ts::command_result{error::group_invalid_id}; } - bool updateList{false}; - for (const auto &ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::CHANNEL_GROUP, - permission::v2::PermissionUpdateType::set_value, - 0, "", - channelGroup->group_id(), channelGroup->display_name()); + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); - updateList |= ppermission.is_group_property(); - } - - if (this->server) { - if (updateList) { - 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())) { - cl->task_update_needed_permissions.enqueue(); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - client_channel_lock.unlock(); - - cl->task_update_channel_client_properties.enqueue(); - }); - } - - return pparser.build_command_result(); + auto target_server = group_manager->channel_groups() == owning_manager ? this->server : nullptr; + return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::set_value); } command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); + auto group_id = cmd["cgid"].as(); + + std::shared_ptr owning_manager{}; 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); + auto group = group_manager->channel_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if (!pparser.validate(this->ref(), 0)) - return pparser.build_command_result(); - - bool updateList{false}; - for (const auto &ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::CHANNEL_GROUP, - permission::v2::PermissionUpdateType::delete_value, - 0, "", - channelGroup->group_id(), channelGroup->display_name()); - updateList |= ppermission.is_group_property(); + if(!group) { + return ts::command_result{error::group_invalid_id}; } - if (this->server) { - if (updateList) - 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())) { - cl->task_update_needed_permissions.enqueue(); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - client_channel_lock.unlock(); + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); - cl->task_update_channel_client_properties.enqueue(); - }); - } - - return pparser.build_command_result(); + auto target_server = group_manager->channel_groups() == owning_manager ? this->server : nullptr; + return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::delete_value); } //TODO: Test if parent or previous is deleted! @@ -2236,7 +1781,7 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if (!pparser.validate(this->ref(), channel->channelId())) { return pparser.build_command_result(); } @@ -2315,7 +1860,7 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); @@ -2428,7 +1973,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); } - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); @@ -2495,7 +2040,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index 84ffc00..cff4119 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -985,7 +985,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); @@ -1028,7 +1028,7 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), cldbid); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); diff --git a/server/src/client/command_handler/groups.cpp b/server/src/client/command_handler/groups.cpp new file mode 100644 index 0000000..dd9949f --- /dev/null +++ b/server/src/client/command_handler/groups.cpp @@ -0,0 +1,862 @@ +// +// Created by WolverinDEV on 08/03/2021. +// +#include + +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" +#include "../../groups/GroupManager.h" + +#include "helpers.h" +#include "./bulk_parsers.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; + + +command_result ConnectedClient::handleCommandGroupAdd(Command &cmd, GroupTarget group_target) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + log::GroupTarget log_group_target; + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); + log_group_target = log::GroupTarget::SERVER; + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1); + log_group_target = log::GroupTarget::CHANNEL; + break; + + default: + return ts::command_result{error::vs_critical, "internal invalid group target"}; + } + + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + log::GroupType log_group_type; + auto group_type = cmd[0].has("type") ? cmd["type"].as() : groups::GroupType::GROUP_TYPE_NORMAL; + 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; + + 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"}; + } + + std::shared_ptr group; + groups::GroupCreateResult result; + + std::string notify_name; + std::string notify_id_key; + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: { + std::shared_ptr s_group; + result = group_manager->server_groups()->create_group(group_type, cmd["name"].string(), s_group); + group = s_group; + + notify_name = this->notify_response_command("notifyservergroupadded"); + notify_id_key = "sgid"; + break; + } + case GroupTarget::GROUPTARGET_CHANNEL: { + std::shared_ptr c_group; + result = group_manager->channel_groups()->create_group(group_type, cmd["name"].string(), c_group); + group = c_group; + + notify_name = this->notify_response_command("notifychannelgroupadded"); + notify_id_key = "cgid"; + break; + } + + default: + assert(false); + result = groups::GroupCreateResult::DATABASE_ERROR; + break; + } + + switch(result) { + case groups::GroupCreateResult::SUCCESS: + break; + + case groups::GroupCreateResult::NAME_TOO_SHORT: + case groups::GroupCreateResult::NAME_TOO_LONG: + return command_result{error::parameter_invalid, "name"}; + + 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); + assert(!notify_id_key.empty()); + serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log_group_target, log_group_type, group->group_id(), group->display_name(), 0, ""); + + { + ts::command_builder notify{notify_name}; + notify.put_unchecked(0, notify_id_key, group->group_id()); + this->sendCommand(notify); + } + + group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing); + if (this->server) { + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + this->server->enqueue_notify_server_group_list(); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + this->server->enqueue_notify_channel_group_list(); + break; + } + } + + std::deque> server_updates{}; + if(!this->server) { + server_updates = serverInstance->getVoiceServerManager()->serverInstances(); + } else { + server_updates.push_back(this->server); + } + + for(const auto& server : server_updates) { + if(!server) { + continue; + } + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + server->enqueue_notify_server_group_list(); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + server->enqueue_notify_channel_group_list(); + break; + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandGroupCopy(Command &cmd, GroupTarget group_target) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto ref_server = this->server; + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); + + log::GroupTarget log_group_target; + GroupId source_group_id, target_group_id; + std::shared_ptr source_group{}, target_group{}; + bool target_group_global; + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: { + std::shared_ptr owning_manager{}; + + source_group_id = cmd["ssgid"].as(); + target_group_id = cmd[0].has("tsgid") ? cmd["tsgid"].as() : 0; + + source_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, source_group_id); + target_group = group_manager->server_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, target_group_id); + target_group_global = owning_manager != group_manager->server_groups(); + + log_group_target = log::GroupTarget::SERVER; + break; + } + + case GroupTarget::GROUPTARGET_CHANNEL: { + std::shared_ptr owning_manager{}; + + source_group_id = cmd["scgid"].as(); + target_group_id = cmd[0].has("tcgid") ? cmd["tcgid"].as() : 0; + + source_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, source_group_id); + target_group = group_manager->channel_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, target_group_id); + target_group_global = owning_manager != group_manager->channel_groups(); + + log_group_target = log::GroupTarget::CHANNEL; + break; + } + } + + if (!source_group) { + return command_result{error::group_invalid_id, "invalid source group id"}; + } + + if (target_group_id > 0 && !target_group) { + return command_result{error::group_invalid_id, "invalid target group"}; + } + + const auto group_type_modifiable = [&](groups::GroupType type) { + switch (type) { + 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 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 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; + } + return permission::undefined; + }; + + { + auto result = group_type_modifiable(source_group->group_type()); + if (result != permission::undefined) { + return command_result{result}; + } + } + + auto global_update = false; + if (target_group) { + 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}; + } + + { + auto result = group_type_modifiable(target_group->group_type()); + if (result != permission::undefined) { + return command_result{result}; + } + } + + groups::GroupCopyResult result; + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + result = group_manager->server_groups()->copy_group_permissions(source_group_id, target_group_id); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + result = group_manager->channel_groups()->copy_group_permissions(source_group_id, target_group_id); + break; + } + + 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}; + + case groups::GroupCopyResult::NAME_INVALID: + default: + return command_result{error::vs_critical}; + } + + log::GroupType log_group_type; + switch (target_group->group_type()) { + case groups::GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case groups::GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + case groups::GroupType::GROUP_TYPE_NORMAL: + case groups::GroupType::GROUP_TYPE_UNKNOWN: + default: + log_group_type = log::GroupType::NORMAL; + break; + } + + serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->group_type() != groups::GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log_group_target, log_group_type, target_group->group_id(), target_group->display_name(), source_group->group_id(), source_group->display_name()); + + global_update = !this->server || target_group_global; + } else { + //Copy to a new group + auto target_type = cmd["type"].as(); + + log::GroupType log_group_type; + switch (target_type) { + case groups::GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case groups::GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + 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"}; + } + + { + 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!"}; + } + + std::shared_ptr created_group{}; + std::string notify_name, notify_id_key; + groups::GroupCopyResult result; + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: { + std::shared_ptr s_group{}; + result = group_manager->server_groups()->copy_group(source_group_id, target_type, cmd["name"].string(), s_group); + created_group = s_group; + + notify_name = this->notify_response_command("notifyservergroupcopied"); + notify_id_key = "sgid"; + break; + } + case GroupTarget::GROUPTARGET_CHANNEL: { + std::shared_ptr c_group{}; + result = group_manager->channel_groups()->copy_group(source_group_id, target_type, cmd["name"].string(), c_group); + created_group = c_group; + + notify_name = this->notify_response_command("notifychannelgroupcopied"); + notify_id_key = "cgid"; + break; + } + default: { + result = groups::GroupCopyResult::DATABASE_ERROR; + break; + } + } + + 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}; + + case groups::GroupCopyResult::NAME_INVALID: + return command_result{error::parameter_invalid, "name"}; + + default: + return command_result{error::vs_critical}; + } + + assert(!notify_name.empty()); + assert(created_group); + serverInstance->action_logger()->group_logger.log_group_create(target_type != groups::GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log_group_target, log_group_type, created_group->group_id(), cmd["name"], source_group->group_id(), source_group->display_name()); + + { + ts::command_builder notify{notify_name}; + notify.put_unchecked(0, notify_id_key, created_group->group_id()); + this->sendCommand(notify); + } + + global_update = !this->server; + } + + std::deque> server_updates{}; + if(global_update) { + server_updates = serverInstance->getVoiceServerManager()->serverInstances(); + } else { + server_updates.push_back(this->server); + } + + for(const auto& server : server_updates) { + if(!server) { + continue; + } + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + server->enqueue_notify_server_group_list(); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + server->enqueue_notify_channel_group_list(); + break; + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandGroupRename(Command &cmd, GroupTarget group_target) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + + log::GroupTarget log_group_target; + std::shared_ptr group{}; + GroupId group_id; + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + group_id = cmd["sgid"].as(); + group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); + + if(!group) { + return ts::command_result{error::group_invalid_id}; + } + + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); + + log_group_target = log::GroupTarget::SERVER; + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + group_id = cmd["cgid"].as(); + group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); + + if(!group) { + return ts::command_result{error::group_invalid_id}; + } + + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + + log_group_target = log::GroupTarget::CHANNEL; + break; + + default: + return ts::command_result{error::vs_critical, "internal invalid group target"}; + } + assert(group); + + auto type = group->group_type(); + log::GroupType log_group_type; + 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 == 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 = group->display_name(); + groups::GroupRenameResult rename_result; + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + rename_result = group_manager->server_groups()->rename_group(group->group_id(), cmd["name"].string()); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + rename_result = group_manager->channel_groups()->rename_group(group->group_id(), cmd["name"].string()); + break; + + default: + assert(false); + rename_result = groups::GroupRenameResult::DATABASE_ERROR; + break; + } + + switch(rename_result) { + case groups::GroupRenameResult::SUCCESS: + break; + + 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_group_target, log_group_type, group->group_id(), group->display_name(), old_name); + std::deque> server_updates{}; + if(!this->server) { + server_updates = serverInstance->getVoiceServerManager()->serverInstances(); + } else { + server_updates.push_back(this->server); + } + + for(const auto& server : server_updates) { + if(!server) { + continue; + } + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + server->enqueue_notify_server_group_list(); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + server->enqueue_notify_channel_group_list(); + break; + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandGroupDel(Command &cmd, GroupTarget group_target) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto ref_server = this->server; + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); + + log::GroupTarget log_group_target; + std::shared_ptr group{}; + GroupId group_id; + + switch (group_target) { + case GroupTarget::GROUPTARGET_SERVER: + group_id = cmd["sgid"].as(); + group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); + + if(!group) { + return ts::command_result{error::group_invalid_id}; + } + + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); + + log_group_target = log::GroupTarget::SERVER; + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + group_id = cmd["cgid"].as(); + group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); + + if(!group) { + return ts::command_result{error::group_invalid_id}; + } + + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + + log_group_target = log::GroupTarget::CHANNEL; + break; + + default: + return ts::command_result{error::vs_critical, "internal invalid group target"}; + } + assert(group); + + /* Test if the group is one of the default group */ + switch (group_target) { + case GroupTarget::GROUPTARGET_SERVER: + if(this->server) { + if(this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete default server group!"}; + } + } + + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"}; + } + + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default server group!"}; + } + + if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"}; + } + + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + if (this->server) { + if (this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] == 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] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete default channel admin group!"}; + } + } + + if (serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default channel group!"}; + } + + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == group->group_id()) { + return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"}; + } + + break; + } + + log::GroupType log_group_type; + switch (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: + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_delete, 1); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_delete, 1); + break; + } + + log_group_type = log::GroupType::NORMAL; + break; + + case groups::GroupType::GROUP_TYPE_UNKNOWN: + default: + return ts::command_result{error::vs_critical}; + } + + auto force_delete = cmd[0].has("force") ? cmd["force"].as() : false; + + groups::GroupDeleteResult result; + switch (group_target) { + case GroupTarget::GROUPTARGET_SERVER: + if(!force_delete && !group_manager->assignments().is_server_group_empty(group->group_id())) { + result = groups::GroupDeleteResult::GROUP_NOT_EMPTY; + break; + } + + result = group_manager->server_groups()->delete_group(group->group_id()); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + if(!force_delete && !group_manager->assignments().is_channel_group_empty(group->group_id())) { + result = groups::GroupDeleteResult::GROUP_NOT_EMPTY; + break; + } + + result = group_manager->channel_groups()->delete_group(group->group_id()); + break; + } + + switch(result) { + case groups::GroupDeleteResult::SUCCESS: + break; + + case groups::GroupDeleteResult::GROUP_NOT_EMPTY: + return ts::command_result{error::group_not_empty}; + + 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_group_target, log_group_type, group->group_id(), group->display_name()); + + std::deque> server_updates{}; + if(!this->server) { + server_updates = serverInstance->getVoiceServerManager()->serverInstances(); + } else { + server_updates.push_back(this->server); + } + + for(const auto& server : server_updates) { + if(!server) { + continue; + } + + switch(group_target) { + case GroupTarget::GROUPTARGET_SERVER: + server->enqueue_notify_server_group_list(); + server->group_manager()->assignments().handle_server_group_deleted(group->group_id()); + server->tokenManager->handle_server_group_deleted(group->group_id()); + break; + + case GroupTarget::GROUPTARGET_CHANNEL: + server->enqueue_notify_channel_group_list(); + server->group_manager()->assignments().handle_channel_group_deleted(group->group_id()); + server->tokenManager->handle_channel_group_deleted(group->group_id()); + break; + } + + this->server->forEachClient([&](const std::shared_ptr& client) { + bool groups_changed; + client->update_displayed_client_groups(groups_changed, groups_changed); + + if(groups_changed) { + client->task_update_needed_permissions.enqueue(); + client->task_update_channel_client_properties.enqueue(); + } + }); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::executeGroupPermissionEdit(Command &cmd, + const std::vector> &groups, + const std::shared_ptr &target_server, + permission::v2::PermissionUpdateType mode) { + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, mode == permission::v2::PermissionUpdateType::set_value}; + if (!pparser.validate(this->ref(), 0)) { + return pparser.build_command_result(); + } + + bool update_channel_group_list{false}, update_server_group_list{false}; + for(const auto& group : groups) { + bool* update_group_list; + log::PermissionTarget log_group_target; + + if(dynamic_pointer_cast(group)) { + update_group_list = &update_server_group_list; + log_group_target = log::PermissionTarget::SERVER_GROUP; + } else { + update_group_list = &update_channel_group_list; + log_group_target = log::PermissionTarget::CHANNEL_GROUP; + } + + for (const auto &ppermission : pparser.iterate_valid_permissions()) { + ppermission.apply_to(group->permissions(), mode); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log_group_target, + mode, + 0, "", + group->group_id(), group->display_name()); + + *update_group_list |= ppermission.is_group_property(); + } + } + + std::deque> server_updates{}; + if(target_server) { + server_updates.push_back(target_server); + } else { + server_updates = serverInstance->getVoiceServerManager()->serverInstances(); + } + + for(const auto& server : server_updates) { + if(!server) { + continue; + } + + if(update_server_group_list) { + server->enqueue_notify_server_group_list(); + } + + if(update_channel_group_list) { + server->enqueue_notify_channel_group_list(); + } + + btree::set updated_clients{}; + for(const auto& group : groups) { + if(auto s_group{dynamic_pointer_cast(group)}; s_group) { + server->forEachClient([&](const std::shared_ptr &client) { + if(updated_clients.count(client->getClientId()) > 0) { + return; + } + + if(client->serverGroupAssigned(s_group)) { + updated_clients.emplace(client->getClientId()); + client->task_update_channel_client_properties.enqueue(); + client->task_update_needed_permissions.enqueue(); + client->join_state_id++; + } + }); + } else if(auto c_group{dynamic_pointer_cast(group)}; c_group) { + server->forEachClient([&](const std::shared_ptr &client) { + if(updated_clients.count(client->getClientId()) > 0) { + return; + } + + auto channel = client->getChannel(); + if(client->channelGroupAssigned(c_group, channel)) { + updated_clients.emplace(client->getClientId()); + client->task_update_channel_client_properties.enqueue(); + client->task_update_needed_permissions.enqueue(); + client->join_state_id++; + } + }); + } else { + assert(false); + } + } + } + + return pparser.build_command_result(); +} diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index f3915dd..1275728 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -461,7 +461,7 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) } } - shared_lock server_channel_lock{this->server->channel_tree_lock}; /* ensure we dont get moved or somebody could move us */ + std::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) { @@ -524,17 +524,23 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) bool channel_group_changed, server_group_changed; targetClient->update_displayed_client_groups(server_group_changed, channel_group_changed); + auto client_channel = targetClient->getChannel(); + if(!client_channel) { + continue; + } + if(channel_group_changed) { targetClient->task_update_needed_permissions.enqueue(); targetClient->task_update_displayed_groups.enqueue(); + std::optional notify{}; 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); + viewer->notifyClientChannelGroupChanged( + notify, + this->ref(), targetClient, + client_channel->channelId(), + targetClient->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID], + targetClient->properties()[property::CLIENT_CHANNEL_GROUP_ID]); } } } @@ -2205,7 +2211,6 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { return command_result{error::channel_invalid_id}; } - auto channel_group = this->assignedChannelGroup(channel); auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), client_dbid); Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : ""); @@ -2302,14 +2307,18 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { } } - { - auto permission_manager = channel_group->group->permissions(); + auto interited_channel{channel}; + auto channel_group = this->assignedChannelGroup(interited_channel); + if(channel_group) { + assert(interited_channel); + + auto permission_manager = channel_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"] = 3; /* channel group */ - result[index]["id1"] = channel_group->channelId; - result[index]["id2"] = channel_group->group->groupId(); + result[index]["id1"] = interited_channel->channelId(); + result[index]["id2"] = channel_group->group_id(); result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; @@ -2319,8 +2328,8 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { } if(permission.flags.grant_set) { result[index]["t"] = 3; /* channel group */ - result[index]["id1"] = channel_group->channelId; - result[index]["id2"] = channel_group->group->groupId(); + result[index]["id1"] = interited_channel->channelId(); + result[index]["id2"] = channel_group->group_id(); result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index ca9e2c2..e1948d0 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -557,7 +557,7 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); @@ -587,7 +587,7 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); @@ -719,7 +719,7 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command & if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) return command_result{perr}; - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if(!pparser.validate(this->ref(), this->getClientDatabaseId())) return pparser.build_command_result(); @@ -753,7 +753,7 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command & return command_result{perr}; - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; + ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if(!pparser.validate(this->ref(), this->getClientDatabaseId())) return pparser.build_command_result(); diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp index 6cda4b0..e2c61b2 100644 --- a/server/src/client/command_handler/server.cpp +++ b/server/src/client/command_handler/server.cpp @@ -7,7 +7,6 @@ #include #include #include -#include "../../build.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../../server/VoiceServer.h" @@ -19,6 +18,8 @@ #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include "../../manager/ActionLogger.h" +#include "../../groups/GroupManager.h" +#include "../../PermissionCalculator.h" #include "helpers.h" #include "./bulk_parsers.h" @@ -27,7 +28,6 @@ #include #include #include -#include #include using namespace std::chrono; @@ -64,9 +64,12 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { auto target_server = this->server; if(cmd[0].has("sid")) { target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]); - if(!target_server && cmd["sid"].as() != 0) return command_result{error::server_invalid_id}; + if(!target_server && cmd["sid"].as() != 0) { + return command_result{error::server_invalid_id}; + } } ServerId serverId = target_server ? target_server->serverId : 0; + auto group_manager = target_server ? target_server->group_manager() : serverInstance->group_manager(); auto cache = make_shared(); map toApplay; @@ -87,23 +90,31 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { SERVEREDIT_CHK_PROP_CACHED("virtualserver_codec_encryption_mode", permission::b_virtualserver_modify_codec_encryption_mode, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_server_group", permission::b_virtualserver_modify_default_servergroup, GroupId) if(target_server) { - auto group = target_server->group_manager()->findGroup(cmd["virtualserver_default_server_group"].as()); - if (!group || group->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + auto target_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["virtualserver_default_server_group"].as()); + if (target_group) { + return command_result{error::group_invalid_id}; + } } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_group", permission::b_virtualserver_modify_default_channelgroup, GroupId) if(target_server) { - auto group = target_server->group_manager()->findGroup(cmd["virtualserver_default_channel_group"].as()); - if (!group || group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid}; + auto target_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["virtualserver_default_channel_group"].as()); + if (target_group) { + return command_result{error::group_invalid_id}; + } } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_admin_group", permission::b_virtualserver_modify_default_channeladmingroup, GroupId) if(target_server) { - auto group = target_server->group_manager()->findGroup(cmd["virtualserver_default_channel_admin_group"].as()); - if (!group || group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid}; + auto target_group = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["virtualserver_default_channel_admin_group"].as()); + if (target_group) { + return command_result{error::group_invalid_id}; + } } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_music_group", permission::b_virtualserver_modify_default_musicgroup, GroupId) if(target_server) { - auto group = target_server->group_manager()->findGroup(cmd["virtualserver_default_music_group"].as()); - if (!group || group->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + auto target_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["virtualserver_default_server_group"].as()); + if (target_group) { + return command_result{error::group_invalid_id}; + } } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_priority_speaker_dimm_modificator", permission::b_virtualserver_modify_priority_speaker_dimm_modificator, float) } @@ -224,15 +235,21 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { } if(target_server) { - if (group_update) - target_server->forEachClient([&](const shared_ptr& client) { - if(target_server->notifyClientPropertyUpdates(client, target_server->group_manager()->update_server_group_property(client, true, client->getChannel()))) { + if (group_update) { + target_server->forEachClient([&](const shared_ptr &client) { + bool groups_changed; + client->update_displayed_client_groups(groups_changed, groups_changed); + + if (groups_changed) { client->task_update_needed_permissions.enqueue(); + client->task_update_channel_client_properties.enqueue(); } }); + } - if (!keys.empty()) + if (!keys.empty()) { target_server->notifyServerEdited(this->ref(), keys); + } } return command_result{error::ok}; } @@ -281,310 +298,33 @@ command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command return command_result{error::ok}; } +command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) { + return this->handleCommandGroupAdd(cmd, GroupTarget::GROUPTARGET_SERVER); +} + +command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { + return this->handleCommandGroupCopy(cmd, GroupTarget::GROUPTARGET_SERVER); +} + +command_result ConnectedClient::handleCommandServerGroupRename(Command &cmd) { + return this->handleCommandGroupRename(cmd, GroupTarget::GROUPTARGET_SERVER); +} + +command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { + return this->handleCommandGroupDel(cmd, GroupTarget::GROUPTARGET_SERVER); +} + command_result ConnectedClient::handleCommandServerGroupList(Command &) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_list, 1); - this->notifyServerGroupList(this->getType() != ClientType::CLIENT_QUERY); + std::optional generated_command{}; + this->notifyServerGroupList(generated_command, this->getType() != ClientType::CLIENT_QUERY); this->command_times.servergrouplist = system_clock::now(); return command_result{error::ok}; } -//servergroupadd name=TestGroup type=1 -command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) { - CMD_RESET_IDLE; - CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1); - - if(cmd["name"].string().empty()) { - return command_result{error::parameter_invalid, "name"}; - } - - log::GroupType log_group_type; - if(!cmd[0].has("type")) { - cmd["type"] = GroupType::GROUP_TYPE_NORMAL; - } - - 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!"}; - } - log_group_type = log::GroupType::NORMAL; - } - - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - for(const auto& gr : group_manager->availableServerGroups(true)) { - if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) { - return command_result{error::parameter_invalid, "Group already exists"}; - } - } - - auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as(), cmd["name"].string()); - if(!group) { - return command_result{error::vs_critical}; - } - - group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing); - - { - ts::command_builder notify{this->notify_response_command("notifyservergroupadded")}; - notify.put_unchecked(0, "sgid", group->groupId()); - this->sendCommand(notify); - } - - if(this->server) { - if(this->getType() == ClientType::CLIENT_QUERY) { - this->server->forEachClient([&](const shared_ptr& cl) { - if(cl == this) { - return; - } - - cl->notifyServerGroupList(); - }); - } else { - this->server->forEachClient([&](const shared_ptr& cl) { - cl->notifyServerGroupList(); - }); - } - } - serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, group->groupId(), group->name(), 0, ""); - return command_result{error::ok}; -} - -command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { - CMD_RESET_IDLE; - CMD_CHK_AND_INC_FLOOD_POINTS(5); - - auto ref_server = this->server; - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1); - - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - - auto source_group_id = cmd["ssgid"].as(); - auto source_group = group_manager->findGroup(source_group_id); - - if(!source_group || source_group->target() != GROUPTARGET_SERVER) - return command_result{error::group_invalid_id}; - - const auto group_type_modificable = [&](GroupType type) { - switch(type) { - case 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))) - return permission::b_serverinstance_modify_querygroup; - break; - - case GroupType::GROUP_TYPE_NORMAL: - default: - break; - } - return permission::undefined; - }; - - { - auto result = group_type_modificable(source_group->type()); - if(result != permission::undefined) - return command_result{result}; - } - - auto global_update = false; - if(cmd[0].has("tsgid") && cmd["tsgid"].as() != 0) { - //Copy an existing group - auto target_group = group_manager->findGroup(cmd["tsgid"]); - if(!target_group || target_group->target() != GROUPTARGET_SERVER) - return command_result{error::group_invalid_id}; - - { - auto result = group_type_modificable(target_group->type()); - if(result != permission::undefined) - return command_result{result}; - } - - if(!target_group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) - return command_result{permission::i_server_group_modify_power}; - - if(!group_manager->copyGroupPermissions(source_group, target_group)) - return command_result{error::vs_critical, "failed to copy group permissions"}; - - - log::GroupType log_group_type; - switch (target_group->type()) { - case GroupType::GROUP_TYPE_QUERY: - log_group_type = log::GroupType::QUERY; - break; - - case GroupType::GROUP_TYPE_TEMPLATE: - log_group_type = log::GroupType::TEMPLATE; - break; - - case GroupType::GROUP_TYPE_NORMAL: - log_group_type = log::GroupType::NORMAL; - break; - - default: - return command_result{error::parameter_invalid, "type"}; - } - - serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), - this->ref(), log::GroupTarget::SERVER, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name()); - - global_update = !this->server || !group_manager->isLocalGroup(target_group); - } else { - //Copy a new group - auto target_type = cmd["type"].as(); - - { - auto result = group_type_modificable(target_type); - if(result != permission::undefined) - return command_result{result}; - } - log::GroupType log_group_type; - switch (target_type) { - case GroupType::GROUP_TYPE_QUERY: - log_group_type = log::GroupType::QUERY; - break; - - case GroupType::GROUP_TYPE_TEMPLATE: - log_group_type = log::GroupType::TEMPLATE; - break; - - case GroupType::GROUP_TYPE_NORMAL: - log_group_type = log::GroupType::NORMAL; - break; - - default: - return command_result{error::parameter_invalid, "type"}; - } - - if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) - return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; - - if(!group_manager->findGroup(GroupTarget::GROUPTARGET_SERVER, cmd["name"].string()).empty()) - return command_result{error::group_name_inuse, "You cant create normal groups on the template server!"}; - - 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::SERVER, log_group_type, target_group_id, cmd["name"].string(), source_group->groupId(), source_group->name()); - if(this->getType() == ClientType::CLIENT_QUERY) { - Command notify(""); - notify["sgid"] = target_group_id; - this->sendCommand(notify); - } - - global_update = !this->server || !group_manager->isLocalGroup(group_manager->findGroup(target_group_id)); - } - - for(const auto& server : (global_update ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) - if(server) - server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - return command_result{error::ok}; -} - -command_result ConnectedClient::handleCommandServerGroupRename(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 serverGroup = group_manager->findGroup(cmd["sgid"].as()); - if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; - ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); - - auto type = serverGroup->type(); - log::GroupType log_group_type; - if(type == GroupType::GROUP_TYPE_QUERY) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); - log_group_type = log::GroupType::QUERY; - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); - log_group_type = log::GroupType::TEMPLATE; - } else { - log_group_type = log::GroupType::NORMAL; - } - - auto old_name = serverGroup->name(); - group_manager->renameGroup(serverGroup, cmd["name"].string()); - serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name(), old_name); - if(this->server) - this->server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - - return command_result{error::ok}; -} - -//servergroupdel sgid=2 force=0 -command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { - CMD_RESET_IDLE; - CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_delete, 1); - - auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager().get(); - auto serverGroup = group_manager->findGroup(cmd["sgid"].as()); - if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; - ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); - - if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId()) - return command_result{error::parameter_invalid, "Could not delete default server group!"}; - - if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId()) - return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"}; - - if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId()) - return command_result{error::parameter_invalid, "Could not delete instance default server group!"}; - - if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId()) - return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"}; - - auto type = serverGroup->type(); - log::GroupType log_group_type; - if(type == GroupType::GROUP_TYPE_QUERY) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); - log_group_type = log::GroupType::QUERY; - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); - log_group_type = log::GroupType::TEMPLATE; - } else { - log_group_type = log::GroupType::NORMAL; - } - - if (!cmd["force"].as()) - if (!group_manager->listGroupMembers(serverGroup, false).empty()) - return command_result{error::database_empty_result, "group not empty!"}; - - if (group_manager->deleteGroup(serverGroup)) { - serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name()); - if(this->server) - this->server->forEachClient([&](shared_ptr 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->notifyServerGroupList(); - }); - } - - if(this->server) { - this->server->tokenManager->handle_server_group_deleted(serverGroup->groupId()); - } - - return command_result{error::ok}; -} - //servergroupclientlist sgid=2 //notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw= command_result ConnectedClient::handleCommandServerGroupClientList(Command &cmd) { @@ -593,23 +333,39 @@ command_result ConnectedClient::handleCommandServerGroupClientList(Command &cmd) ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_client_list, 1); - auto server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; - auto groupManager = server ? this->server->group_manager() : serverInstance->group_manager().get(); - - auto serverGroup = groupManager->findGroup(cmd["sgid"].as()); - if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; - - Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyservergroupclientlist" : ""); - notify["sgid"] = cmd["sgid"].as(); - int index = 0; - for (const auto &clientEntry : groupManager->listGroupMembers(serverGroup)) { - notify[index]["cldbid"] = clientEntry.cldbId; - notify[index]["client_nickname"] = clientEntry.displayName; - notify[index]["client_unique_identifier"] = clientEntry.uid; - index++; + std::shared_ptr target_server{}; + if(cmd[0].has("sid")) { + auto server_id = cmd["sid"].as(); + if(server_id == 0) { + /* that's all right */ + target_server = this->server; + } else if(server_id == this->getServerId()) { + /* this is also allowed */ + } else { + return ts::command_result{error::server_invalid_id}; + } + } else { + target_server = this->server; } - if(index == 0 && this->getType() != ClientType::CLIENT_TEAMSPEAK) { + auto group_manager = target_server ? target_server->group_manager() : serverInstance->group_manager(); + auto server_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["sgid"].as()); + if (!server_group) { + return command_result{error::group_invalid_id}; + } + + ts::command_builder notify{this->notify_response_command("notifyservergroupclientlist")}; + notify.put_unchecked(0, "sgid", server_group->group_id()); + + int index{0}; + for (const auto &assignment : group_manager->assignments().server_group_clients(server_group->group_id(), true)) { + auto bulk = notify.bulk(index++); + bulk.put_unchecked("cldbid", assignment.client_database_id); + bulk.put_unchecked("client_nickname", assignment.client_display_name.value_or("")); + bulk.put_unchecked("client_unique_identifier", assignment.client_unique_id.value_or("")); + } + + if(index == 0) { return ts::command_result{error::database_empty_result}; } @@ -621,120 +377,120 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto target_server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; - auto group_manager = target_server ? this->server->group_manager() : serverInstance->group_manager().get(); - - auto target_cldbid = cmd["cldbid"].as(); - if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; - - auto needed_client_permission = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0); - if(needed_client_permission.has_value) { - if(!permission::v2::permission_granted(needed_client_permission, this->calculate_permission(permission::i_client_permission_modify_power, 0))) - return command_result{permission::i_client_needed_permission_modify_power}; + std::shared_ptr target_server{}; + if(cmd[0].has("sid")) { + auto server_id = cmd["sid"].as(); + if(server_id == 0) { + /* that's all right */ + } else if(server_id == this->getServerId()) { + /* this is also allowed */ + target_server = this->server; + } else { + return ts::command_result{error::server_invalid_id}; + } + } else { + target_server = this->server; } - vector> target_groups; - vector> applied_groups; - target_groups.reserve(cmd.bulkCount()); + auto group_manager = target_server ? this->server->group_manager() : serverInstance->group_manager(); + + auto target_cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) { + return command_result{error::client_invalid_id, "invalid cldbid"}; + } + + ClientPermissionCalculator client_permissions{target_server, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0}; + if(!permission::v2::permission_granted(client_permissions.calculate_permission(permission::i_client_needed_permission_modify_power), this->calculate_permission(permission::i_client_permission_modify_power, 0))) { + return command_result{permission::i_client_needed_permission_modify_power}; + } + + std::vector> added_groups{}; + added_groups.reserve(cmd.bulkCount()); + + ts::command_result_bulk result{}; + result.reserve(cmd.bulkCount()); - auto continue_on_error = cmd.hasParm("continueonerror"); { auto permission_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1); auto permission_self_add_power = this->calculate_permission(permission::i_server_group_self_add_power, -1); for(auto index = 0; index < cmd.bulkCount(); index++) { - auto group_id = cmd[index]["sgid"]; - if(!group_id.castable() && continue_on_error) - continue; - - auto gid = group_id.as(); - auto group = group_manager->findGroup(gid); + auto group_id = cmd[index]["sgid"].as(); + auto group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { - if(continue_on_error) - continue; - - return command_result{error::group_invalid_id}; - } - - if(!target_server && group->target() != GroupTarget::GROUPTARGET_SERVER && group->type() != GroupType::GROUP_TYPE_QUERY) - return command_result{error::parameter_invalid}; - - if(find(target_groups.begin(), target_groups.end(), group) != target_groups.end()) { - if(continue_on_error) - continue; - - return command_result{error::client_is_already_member_of_group}; + result.emplace_result(error::group_invalid_id); + continue; } /* permission tests */ if(!group->permission_granted(permission::i_server_group_needed_member_add_power, permission_add_power, true)) { if(target_cldbid != this->getClientDatabaseId()) { - if(continue_on_error) - continue; - - return command_result{permission::i_server_group_member_add_power}; + result.emplace_result(permission::i_server_group_member_add_power); + continue; } if(!group->permission_granted(permission::i_server_group_needed_member_add_power, permission_self_add_power, true)) { - if(continue_on_error) - continue; - - return command_result{permission::i_server_group_self_add_power}; - } - } - - target_groups.push_back(std::move(group)); - } - } - - applied_groups.reserve(target_groups.size()); - if(target_groups.empty()) return command_result{error::ok}; - else if(target_groups.size() == 1) { - /* speed up thing, don't try to load any cache */ - auto group = target_groups[0]; - if(group_manager->hasServerGroupAssigned(target_cldbid, group)) - return command_result{error::client_is_already_member_of_group}; - - group_manager->addServerGroup(target_cldbid, group); - applied_groups.push_back(group); - } else { - group_manager->enableCache(target_cldbid); - scope_exit_callback cache_disable{[group_manager, target_cldbid]{ - group_manager->disableCache(target_cldbid); - }}; - - for(const auto& group : target_groups) { - if(group_manager->hasServerGroupAssigned(target_cldbid, group)) { - if(continue_on_error) + result.emplace_result(permission::i_server_group_self_add_power); continue; - return command_result{error::client_is_already_member_of_group}; - } - - group_manager->addServerGroup(target_cldbid, group); - applied_groups.push_back(group); - } - } - - std::shared_ptr connected_client{}; - for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { - for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { - connected_client = targetClient; - if (_server->notifyClientPropertyUpdates(targetClient, _server->group_manager()->update_server_group_property(targetClient, true, targetClient->getChannel()))) { - for (const auto &client : _server->getClients()) { - if(client->isClientVisible(targetClient, true) || client == targetClient) - for(const auto& group : applied_groups) - client->notifyServerGroupClientAdd(this->ref(), targetClient, group); } - targetClient->task_update_needed_permissions.enqueue(); - targetClient->task_update_channel_client_properties.enqueue(); + } + + switch (group_manager->assignments().add_server_group(target_cldbid, group_id, !group->is_permanent())) { + case groups::GroupAssignmentResult::SUCCESS: + break; + + case groups::GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP: + result.emplace_result(error::client_is_already_member_of_group); + continue; + + case groups::GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP: + case groups::GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP: + default: + result.emplace_result(error::vs_critical); + continue; + } + + added_groups.push_back(group); + result.emplace_result(error::ok); + } + } + + std::deque> updated_servers{}; + if(target_server) { + updated_servers.push_back(target_server); + } else { + updated_servers = serverInstance->getVoiceServerManager()->serverInstances(); + } + + auto invoker = this->ref(); + std::shared_ptr client_instance{}; + for(const auto& updated_server : updated_servers) { + for (const auto &updated_client : updated_server->findClientsByCldbId(target_cldbid)) { + client_instance = updated_client; + + bool groups_changed; + updated_client->update_displayed_client_groups(groups_changed, groups_changed); + + if(groups_changed) { + updated_client->task_update_needed_permissions.enqueue(); + updated_client->task_update_channel_client_properties.enqueue(); + } + + auto client_list = updated_server->getClients(); + for(const auto& group : added_groups) { + std::optional notify{}; + for(const auto& client : client_list) { + client->notifyServerGroupClientAdd(notify, invoker, updated_client, group->group_id()); + } } } } - for(const auto& group : applied_groups) { + + for(const auto& group : added_groups) { serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(target_server ? target_server->getServerId() : 0, this->ref(), log::GroupTarget::SERVER, - group->groupId(), group->name(), - target_cldbid, connected_client ? connected_client->getDisplayName() : "" + group->group_id(), group->display_name(), + target_cldbid, client_instance ? client_instance->getDisplayName() : "" ); } @@ -745,134 +501,120 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto target_server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; - auto group_manager = target_server ? this->server->group_manager() : serverInstance->group_manager().get(); - - auto target_cldbid = cmd["cldbid"].as(); - if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; - - auto needed_client_permission = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0); - if(needed_client_permission.has_value) { - if(!permission::v2::permission_granted(needed_client_permission, this->calculate_permission(permission::i_client_permission_modify_power, 0))) - return command_result{permission::i_client_needed_permission_modify_power}; + std::shared_ptr target_server{}; + if(cmd[0].has("sid")) { + auto server_id = cmd["sid"].as(); + if(server_id == 0) { + /* that's all right */ + } else if(server_id == this->getServerId()) { + /* this is also allowed */ + target_server = this->server; + } else { + return ts::command_result{error::server_invalid_id}; + } + } else { + target_server = this->server; } - vector> target_groups; - vector> applied_groups; - target_groups.reserve(cmd.bulkCount()); + auto group_manager = target_server ? this->server->group_manager() : serverInstance->group_manager(); + + auto target_cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) { + return command_result{error::client_invalid_id, "invalid cldbid"}; + } + + ClientPermissionCalculator client_permissions{target_server, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0}; + if(!permission::v2::permission_granted(client_permissions.calculate_permission(permission::i_client_needed_permission_modify_power), this->calculate_permission(permission::i_client_permission_modify_power, 0))) { + return command_result{permission::i_client_needed_permission_modify_power}; + } + + std::vector> removed_groups{}; + removed_groups.reserve(cmd.bulkCount()); + + ts::command_result_bulk result{}; + result.reserve(cmd.bulkCount()); - auto continue_on_error = cmd.hasParm("continueonerror"); { auto permission_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1); auto permission_self_remove_power = this->calculate_permission(permission::i_server_group_self_remove_power, -1); for(auto index = 0; index < cmd.bulkCount(); index++) { - auto group_id = cmd[index]["sgid"]; - if(!group_id.castable() && continue_on_error) - continue; - - auto gid = group_id.as(); - auto group = group_manager->findGroup(gid); + auto group_id = cmd[index]["sgid"].as(); + auto group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { - if(continue_on_error) - continue; - - return command_result{error::group_invalid_id}; - } - - if(!target_server && group->target() != GroupTarget::GROUPTARGET_SERVER && group->type() != GroupType::GROUP_TYPE_QUERY) - return command_result{error::group_invalid_id}; - - if(find(target_groups.begin(), target_groups.end(), group) != target_groups.end()) { - if(continue_on_error) - continue; - - return command_result{error::parameter_invalid, "duplicate server group for id " + to_string(gid)}; + result.emplace_result(error::group_invalid_id); + continue; } /* permission tests */ if(!group->permission_granted(permission::i_server_group_needed_member_remove_power, permission_remove_power, true)) { if(target_cldbid != this->getClientDatabaseId()) { - if(continue_on_error) - continue; - - return command_result{permission::i_server_group_member_remove_power}; + result.emplace_result(permission::i_server_group_member_remove_power); + continue; } if(!group->permission_granted(permission::i_server_group_needed_member_remove_power, permission_self_remove_power, true)) { - if(continue_on_error) - continue; - - return command_result{permission::i_server_group_self_remove_power}; + result.emplace_result(permission::i_server_group_self_remove_power); + continue; } } - target_groups.push_back(std::move(group)); + switch (group_manager->assignments().remove_server_group(target_cldbid, group_id)) { + case groups::GroupAssignmentResult::SUCCESS: + break; + + case groups::GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP: + result.emplace_result(error::client_is_already_member_of_group); + continue; + + case groups::GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP: + case groups::GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP: + default: + result.emplace_result(error::vs_critical); + continue; + } + + removed_groups.push_back(group); + result.emplace_result(error::ok); } } - applied_groups.reserve(target_groups.size()); - if(target_groups.empty()) return command_result{error::ok}; - else if(target_groups.size() == 1) { - /* speed up thing, don't try to load any cache */ - auto group = target_groups[0]; - auto assignment = group_manager->get_group_assignment(target_cldbid, group); - if(!assignment) { - return command_result{error::client_is_not_member_of_group}; - } - if(assignment->server != (target_server ? target_server->getServerId() : 0)) { - return command_result{error::group_not_assigned_over_this_server}; - } - - group_manager->removeServerGroup(target_cldbid, group); - applied_groups.push_back(group); + std::deque> updated_servers{}; + if(target_server) { + updated_servers.push_back(target_server); } else { - group_manager->enableCache(target_cldbid); - scope_exit_callback cache_disable{[group_manager, target_cldbid]{ - group_manager->disableCache(target_cldbid); - }}; - - for(const auto& group : target_groups) { - auto assignment = group_manager->get_group_assignment(target_cldbid, group); - if(!assignment) { - if(continue_on_error) - continue; - return command_result{error::client_is_not_member_of_group}; - } - if(assignment->server != (target_server ? target_server->getServerId() : 0)) { - if(continue_on_error) - continue; - return command_result{error::group_not_assigned_over_this_server}; - } - - applied_groups.push_back(group); - group_manager->removeServerGroup(target_cldbid, group); - } + updated_servers = serverInstance->getVoiceServerManager()->serverInstances(); } - std::shared_ptr connected_client{}; - for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { - for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { - connected_client = targetClient; - ConnectedLockedClient clock{targetClient}; - if(!clock) continue; - - if (_server->notifyClientPropertyUpdates(targetClient, _server->group_manager()->update_server_group_property(targetClient, true, targetClient->getChannel()))) { - for (const auto &client : _server->getClients()) { - if(client->isClientVisible(targetClient, true) || client == targetClient) - for(const auto& group : applied_groups) - client->notifyServerGroupClientRemove(this->ref(), targetClient, group); + auto invoker = this->ref(); + std::shared_ptr client_instance{}; + for(const auto& updated_server : updated_servers) { + for (const auto &updated_client : updated_server->findClientsByCldbId(target_cldbid)) { + client_instance = updated_client; + + bool groups_changed; + updated_client->update_displayed_client_groups(groups_changed, groups_changed); + + if(groups_changed) { + updated_client->task_update_needed_permissions.enqueue(); + updated_client->task_update_channel_client_properties.enqueue(); + } + + auto client_list = updated_server->getClients(); + for(const auto& group : removed_groups) { + std::optional notify{}; + for(const auto& client : client_list) { + client->notifyServerGroupClientRemove(notify, invoker, updated_client, group->group_id()); } - targetClient->task_update_needed_permissions.enqueue(); - targetClient->task_update_channel_client_properties.enqueue(); } } } - for(const auto& group : applied_groups) { + + for(const auto& group : removed_groups) { serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(target_server ? target_server->getServerId() : 0, this->ref(), log::GroupTarget::SERVER, - group->groupId(), group->name(), - target_cldbid, connected_client ? connected_client->getDisplayName() : "" + group->group_id(), group->display_name(), + target_cldbid, client_instance ? client_instance->getDisplayName() : "" ); } @@ -884,328 +626,147 @@ command_result ConnectedClient::handleCommandServerGroupPermList(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_permission_list, 1); - auto serverGroup = (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->findGroup(cmd["sgid"].as()); - if (!serverGroup) return command_result{error::group_invalid_id}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto serverGroup = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["sgid"].as()); + if (!serverGroup) { + return command_result{error::group_invalid_id}; + } if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { this->sendTSPermEditorWarning(); } - if (!this->notifyGroupPermList(serverGroup, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; + + if (!this->notifyGroupPermList(serverGroup, cmd.hasParm("permsid"))) { + return command_result{error::database_empty_result}; + } return command_result{error::ok}; } command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { - CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto serverGroup = (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->findGroup(cmd["sgid"].as()); - if (!serverGroup) return command_result{error::group_invalid_id}; - if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; - ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1); + auto group_id = cmd["sgid"].as(); - /* We don't need this. The modify permissions only apply when creating/editing/renaming the groups itself - auto type = serverGroup->type(); - if(type == GroupType::GROUP_TYPE_QUERY) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); - } - */ + std::shared_ptr owning_manager{}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto group = group_manager->server_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if(!pparser.validate(this->ref(), 0)) - return pparser.build_command_result(); - - bool update_talk_power{false}, update_server_group_list{false}; - auto permissions = serverGroup->permissions(); - for(const auto& ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::SERVER_GROUP, - permission::v2::PermissionUpdateType::set_value, - serverGroup->groupId(), serverGroup->name(), - 0, "" - ); - - update_server_group_list |= ppermission.is_group_property(); - update_talk_power |= ppermission.is_client_view_property(); + if(!group) { + return ts::command_result{error::group_invalid_id}; } - if(update_server_group_list) - serverGroup->apply_properties_from_permissions(); + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); - //TODO may update for every server? - if(this->server) { - auto lock = this->ref(); - auto server = this->server; - threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() { - if(update_server_group_list) - server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - server->forEachClient([serverGroup, update_talk_power](shared_ptr cl) { - if (cl->serverGroupAssigned(serverGroup)) { - cl->task_update_needed_permissions.enqueue(); - if (update_talk_power) - cl->task_update_channel_client_properties.enqueue(); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - }); - }).detach(); - } - - return pparser.build_command_result(); + auto target_server = group_manager->server_groups() == owning_manager ? this->server : nullptr; + return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::set_value); } command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { - CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto serverGroup = (this->server ? this->server->group_manager() : serverInstance->group_manager().get())->findGroup(cmd["sgid"].as()); - if (!serverGroup) return command_result{error::group_invalid_id}; - if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; - ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1); + auto group_id = cmd["sgid"].as(); - /* We don't need this. The modify permissions only apply when creating/editing/renaming the groups itself - auto type = serverGroup->type(); - if(type == GroupType::GROUP_TYPE_QUERY) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); - } - */ + std::shared_ptr owning_manager{}; + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto group = group_manager->server_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if(!pparser.validate(this->ref(), 0)) - return pparser.build_command_result(); - - bool update_talk_power{false}, update_server_group_list{false}; - for(const auto& ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::SERVER_GROUP, - permission::v2::PermissionUpdateType::delete_value, - serverGroup->groupId(), serverGroup->name(), - 0, "" - ); - - update_server_group_list |= ppermission.is_group_property(); - update_talk_power |= ppermission.is_client_view_property(); + if(!group) { + return ts::command_result{error::group_invalid_id}; } - if(update_server_group_list) - serverGroup->apply_properties_from_permissions(); + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); - if(this->server) { - auto lock = this->ref(); - auto server = this->server; - threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() { - if(update_server_group_list) - server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - server->forEachClient([serverGroup, update_talk_power](shared_ptr cl) { - if (cl->serverGroupAssigned(serverGroup)) { - - cl->task_update_needed_permissions.enqueue(); - if (update_talk_power) - cl->task_update_channel_client_properties.enqueue(); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - } - }); - }).detach(); - } - - return pparser.build_command_result(); + auto target_server = group_manager->server_groups() == owning_manager ? this->server : nullptr; + return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::delete_value); } command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); + auto update_type = cmd["sgtype"].as(); + auto ref_server = this->server; - auto group_manager = ref_server ? this->server->group_manager() : &*serverInstance->group_manager(); + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); - deque> groups; - for(const auto& group : group_manager->availableGroups(false)) { - if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { - if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { - auto type = group->type(); - if(type == GroupType::GROUP_TYPE_QUERY) { - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) - continue; - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) - continue; - } - groups.push_back(group);//sgtype - } - } - } + std::vector> target_groups{}; + auto available_groups = group_manager->server_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); + target_groups.reserve(available_groups.size()); - if(groups.empty()) - return command_result{error::ok}; - - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if(!pparser.validate(this->ref(), 0)) - return pparser.build_command_result(); - - bool update_clients{false}, update_server_group_list{false}; - for(const auto& ppermission : pparser.iterate_valid_permissions()) { - for(const auto& serverGroup : groups) { - ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::SERVER_GROUP, - permission::v2::PermissionUpdateType::set_value, - serverGroup->groupId(), serverGroup->name(), - 0, "" - ); + for(const auto& group : available_groups) { + if(group->update_type() != update_type) { + continue; } - update_server_group_list |= ppermission.is_group_property(); - update_clients |= ppermission.is_client_view_property(); + target_groups.push_back(group); } - if(update_server_group_list) - for(auto& group : groups) - group->apply_properties_from_permissions(); - - auto lock = this->ref(); - if(ref_server) { - threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() { - if(update_server_group_list) - ref_server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - ref_server->forEachClient([groups, update_clients](shared_ptr cl) { - for(const auto& serverGroup : groups) { - if (cl->serverGroupAssigned(serverGroup)) { - cl->task_update_needed_permissions.enqueue(); - if (update_clients) { - cl->task_update_channel_client_properties.enqueue(); - } - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */ - break; - } - } - }); - }).detach(); + if(target_groups.empty()) { + return ts::command_result{error::database_empty_result}; } - return pparser.build_command_result(); + return this->executeGroupPermissionEdit(cmd, target_groups, nullptr, permission::v2::PermissionUpdateType::set_value); } command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); + auto update_type = cmd["sgtype"].as(); + auto ref_server = this->server; - auto group_manager = ref_server ? this->server->group_manager() : &*serverInstance->group_manager(); + auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager(); - deque> groups; - for(const auto& group : group_manager->availableGroups(false)) { - if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { - if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { - auto type = group->type(); - if(type == GroupType::GROUP_TYPE_QUERY) { - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) - continue; - } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) - continue; - } - groups.push_back(group);//sgtype - } - } - } + std::vector> target_groups{}; + auto available_groups = group_manager->server_groups()->available_groups(groups::GroupCalculateMode::GLOBAL); + target_groups.reserve(available_groups.size()); - if(groups.empty()) return command_result{error::ok}; - - ts::command::bulk_parser::PermissionBulksParser pparser{cmd}; - if(!pparser.validate(this->ref(), 0)) - return pparser.build_command_result(); - - bool update_clients{false}, update_server_group_list{false}; - for(const auto& ppermission : pparser.iterate_valid_permissions()) { - for(const auto& serverGroup : groups) { - ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); - ppermission.log_update(serverInstance->action_logger()->permission_logger, - this->getServerId(), - this->ref(), - log::PermissionTarget::SERVER_GROUP, - permission::v2::PermissionUpdateType::delete_value, - serverGroup->groupId(), serverGroup->name(), - 0, "" - ); + for(const auto& group : available_groups) { + if(group->update_type() != update_type) { + continue; } - update_server_group_list |= ppermission.is_group_property(); - update_clients |= ppermission.is_client_view_property(); + target_groups.push_back(group); } - if(update_server_group_list) { - for(auto& group : groups) - group->apply_properties_from_permissions(); + if(target_groups.empty()) { + return ts::command_result{error::database_empty_result}; } - if(ref_server) { - auto lock = this->ref(); - threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() { - if(update_server_group_list) - ref_server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - ref_server->forEachClient([groups, update_clients](shared_ptr cl) { - for(const auto& serverGroup : groups) { - if (cl->serverGroupAssigned(serverGroup)) { - cl->task_update_needed_permissions.enqueue(); - if (update_clients) - cl->task_update_channel_client_properties.enqueue(); - cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ - break; - } - } - }); - }).detach(); - } - - return pparser.build_command_result(); + return this->executeGroupPermissionEdit(cmd, target_groups, nullptr, permission::v2::PermissionUpdateType::delete_value); } command_result ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) { CMD_RESET_IDLE; ClientDbId cldbid = cmd["cldbid"]; - if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return command_result{error::client_invalid_id}; - - Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergroupsbyclientid" : ""); - - int index = 0; - if (this->server) { - for (const auto &group : this->server->group_manager()->getAssignedServerGroups(cldbid)) { - result[index]["name"] = group->group->name(); - result[index]["sgid"] = group->group->groupId(); - result[index]["cldbid"] = cldbid; - index++; - } - } else { - for (const auto &group : serverInstance->group_manager()->getAssignedServerGroups(cldbid)) { - result[index]["name"] = group->group->name(); - result[index]["sgid"] = group->group->groupId(); - result[index]["cldbid"] = cldbid; - index++; - } + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) { + return command_result{error::client_invalid_id}; } - if (index == 0) return command_result{error::database_empty_result}; - this->sendCommand(result); + auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); + auto assigned_group_ids = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, cldbid); + if(assigned_group_ids.empty()) { + return ts::command_result{error::database_empty_result}; + } + + ts::command_builder notify{this->notify_response_command("notifyservergroupsbyclientid"), 64, assigned_group_ids.size()}; + size_t index{0}; + for(const auto& group_id : assigned_group_ids) { + auto group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + continue; + } + + auto bulk = notify.bulk(index++); + bulk.put_unchecked("name", group->display_name()); + bulk.put_unchecked("sgid", group->group_id()); + bulk.put_unchecked("cldbid", cldbid); + } + + this->sendCommand(notify); + return command_result{error::ok}; } diff --git a/server/src/client/query/QueryClient.cpp b/server/src/client/query/QueryClient.cpp index b1f565e..da9bf67 100644 --- a/server/src/client/query/QueryClient.cpp +++ b/server/src/client/query/QueryClient.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "../../groups/GroupAssignmentManager.h" using namespace std; using namespace std::chrono; @@ -631,9 +632,6 @@ void QueryClient::disconnect_from_virtual_server(const std::string& reason) { this->currentChannel = nullptr; } - old_server->group_manager()->disableCache(this->getClientDatabaseId()); this->loadDataForCurrentServer(); } - - serverInstance->group_manager()->enableCache(this->getClientDatabaseId()); } \ No newline at end of file diff --git a/server/src/client/query/QueryClient.h b/server/src/client/query/QueryClient.h index e82c078..f3faf86 100644 --- a/server/src/client/query/QueryClient.h +++ b/server/src/client/query/QueryClient.h @@ -134,11 +134,21 @@ namespace ts::server { bool notifyClientChatComposing(const std::shared_ptr &ptr) override; bool notifyClientChatClosed(const std::shared_ptr &ptr) override; bool notifyTextMessage(ChatMessageMode mode, const std::shared_ptr &sender, uint64_t targetId, ChannelId channel_id, const std::chrono::system_clock::time_point&, const std::string &textMessage) override; - bool notifyServerGroupClientAdd(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &group) override; - bool notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) override; - bool notifyClientChannelGroupChanged(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &ptr, const std::shared_ptr &shared_ptr, - const std::shared_ptr &group, bool lock_channel_tree) override; + bool notifyServerGroupClientAdd(std::optional &anOptional, + const std::shared_ptr &ptr, + const std::shared_ptr &sharedPtr, + const GroupId &id) override; + + bool notifyServerGroupClientRemove(std::optional &anOptional, + const std::shared_ptr &ptr, + const std::shared_ptr &sharedPtr, + const GroupId &id) override; + + bool notifyClientChannelGroupChanged(std::optional &anOptional, + const std::shared_ptr &ptr, + const std::shared_ptr &sharedPtr, const ChannelId &id, + const ChannelId &channelId, const GroupId &groupId) override; bool notifyChannelMoved(const std::shared_ptr &channel, ChannelId order, const std::shared_ptr &invoker) override; bool notifyChannelCreate(const std::shared_ptr &channel, ChannelId orderId, diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index 00d7a16..c8b4ef1 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -13,7 +13,7 @@ #include #include #include "src/manager/ActionLogger.h" - +#include "../../groups/GroupManager.h" #include "src/client/command_handler/helpers.h" using namespace std; @@ -244,12 +244,12 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { if(this->server) { { unique_lock tree_lock(this->server->channel_tree_lock); - if(joined_channel) + if(joined_channel) { this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); + } this->server->unregisterClient(this->ref(), "login", tree_lock); } - this->server->group_manager()->disableCache(this->getClientDatabaseId()); - } else serverInstance->group_manager()->disableCache(this->getClientDatabaseId()); + } logMessage(LOG_QUERY, "Got new authenticated client. Username: {}, Unique-ID: {}, Bounded Server: {}", account->username, account->unique_id, account->bound_server); @@ -269,7 +269,6 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { DatabaseHelper::assignDatabaseId(this->sql, static_cast(target_server ? target_server->getServerId() : 0), this->ref()); if(target_server) { - target_server->group_manager()->enableCache(this->getClientDatabaseId()); target_server->registerClient(this->ref()); { @@ -293,7 +292,6 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { this->task_update_needed_permissions.enqueue(); } } else { - serverInstance->group_manager()->enableCache(this->getClientDatabaseId()); this->task_update_needed_permissions.enqueue(); } @@ -318,15 +316,13 @@ command_result QueryClient::handleCommandLogout(Command &) { this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); this->server->unregisterClient(this->ref(), "logout", tree_lock); } - this->server->group_manager()->disableCache(this->getClientDatabaseId()); - } else serverInstance->group_manager()->disableCache(this->getClientDatabaseId()); + } this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = "UnknownQuery"; //TODO load from table this->properties()[property::CLIENT_NICKNAME] = string() + "ServerQuery#" + this->getLoggingPeerIp() + "/" + to_string(ntohs(this->getPeerPort())); DatabaseHelper::assignDatabaseId(this->sql, static_cast(this->server ? this->server->getServerId() : 0), this->ref()); if(this->server){ - this->server->group_manager()->enableCache(this->getClientDatabaseId()); this->server->registerClient(this->ref()); { @@ -347,7 +343,6 @@ command_result QueryClient::handleCommandLogout(Command &) { this->task_update_needed_permissions.enqueue(); } } else { - serverInstance->group_manager()->enableCache(this->getClientDatabaseId()); this->task_update_needed_permissions.enqueue(); } @@ -401,7 +396,6 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { DatabaseHelper::assignDatabaseId(this->sql, static_cast(this->server ? this->server->getServerId() : 0), this->ref()); if(this->server) { - this->server->group_manager()->enableCache(this->getClientDatabaseId()); this->server->registerClient(this->ref()); { @@ -420,7 +414,6 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { this->task_update_needed_permissions.enqueue(); } } else { - serverInstance->group_manager()->enableCache(this->getClientDatabaseId()); this->task_update_needed_permissions.enqueue(); } diff --git a/server/src/client/query/QueryClientNotify.cpp b/server/src/client/query/QueryClientNotify.cpp index ac96953..2f692eb 100644 --- a/server/src/client/query/QueryClientNotify.cpp +++ b/server/src/client/query/QueryClientNotify.cpp @@ -75,20 +75,27 @@ bool QueryClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, const shared_ptr &client, const shared_ptr &group) { + +bool QueryClient::notifyServerGroupClientAdd(optional &anOptional, + const shared_ptr &ptr, + const shared_ptr &sharedPtr, const GroupId &id) { CHK_EVENT(QEVENTGROUP_CLIENT_GROUPS, QEVENTSPECIFIER_CLIENT_GROUPS_ADD); - return ConnectedClient::notifyServerGroupClientAdd(invoker, client, group); + return ConnectedClient::notifyServerGroupClientAdd(anOptional, ptr, sharedPtr, id); } -bool QueryClient::notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) { +bool QueryClient::notifyServerGroupClientRemove(optional &anOptional, + const shared_ptr &ptr, + const shared_ptr &sharedPtr, const GroupId &id) { CHK_EVENT(QEVENTGROUP_CLIENT_GROUPS, QEVENTSPECIFIER_CLIENT_GROUPS_REMOVE); - return ConnectedClient::notifyServerGroupClientRemove(invoker, client, group); + return ConnectedClient::notifyServerGroupClientRemove(anOptional, ptr, sharedPtr, id); } -bool QueryClient::notifyClientChannelGroupChanged(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &ptr, const std::shared_ptr &shared_ptr, - const std::shared_ptr &group, bool lock_channel_tree) { +bool QueryClient::notifyClientChannelGroupChanged(optional &anOptional, + const shared_ptr &ptr, + const shared_ptr &sharedPtr, const ChannelId &id, + const ChannelId &channelId, const GroupId &groupId) { CHK_EVENT(QEVENTGROUP_CLIENT_GROUPS, QEVENTSPECIFIER_CLIENT_GROUPS_CHANNEL_CHANGED); - return ConnectedClient::notifyClientChannelGroupChanged(invoker, client, ptr, shared_ptr, group, lock_channel_tree); + return ConnectedClient::notifyClientChannelGroupChanged(anOptional, ptr, sharedPtr, id, channelId, groupId); } bool QueryClient::notifyChannelMoved(const std::shared_ptr &channel, ChannelId order, const std::shared_ptr &invoker) { diff --git a/server/src/client/shared/WhisperHandler.h b/server/src/client/shared/WhisperHandler.h index 4b4b111..e7af8e3 100644 --- a/server/src/client/shared/WhisperHandler.h +++ b/server/src/client/shared/WhisperHandler.h @@ -73,8 +73,8 @@ namespace ts::server::whisper { * This also updates the last head ptr. * @return True if the packet is a valid whisper packet. The payload ptr and payload length variables will be set */ - bool validate_whisper_packet(const protocol::PacketParser& /* packet */, bool& /* matches last header */, void*& /* payload ptr */, size_t& /* payload length */); - ts::command_result configure_rtc_clients(uint32_t /* stream id */, const std::vector>& /* clients */); + [[nodiscard]] bool validate_whisper_packet(const protocol::PacketParser& /* packet */, bool& /* matches last header */, void*& /* payload ptr */, size_t& /* payload length */); + [[nodiscard]] ts::command_result configure_rtc_clients(uint32_t /* stream id */, const std::vector>& /* clients */); [[nodiscard]] size_t max_whisper_targets(); }; diff --git a/server/src/client/voice/PacketEncoder.cpp b/server/src/client/voice/PacketEncoder.cpp index 42592b9..8e6f943 100644 --- a/server/src/client/voice/PacketEncoder.cpp +++ b/server/src/client/voice/PacketEncoder.cpp @@ -144,6 +144,7 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std: *packets_tail = packet; packets_tail = &packet->next; + assert(!packet->next); data_length -= bytes; if(data_length == 0) { @@ -161,6 +162,7 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std: packets_head = packet; packets_tail = &packet->next; + assert(!packet->next); } { diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index 4a5a445..d9cae7f 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -21,7 +21,7 @@ constexpr static auto kMaxWhisperClientNameLength{30}; constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */ VoiceClient::VoiceClient(const std::shared_ptr& server, const sockaddr_storage* address) : - SpeakingClient(server->server->sql, server->server), + SpeakingClient{server->server->sql, server->server}, voice_server(server) { assert(address); memtrack::allocated(this); diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index 0f692cb..90f1196 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -227,7 +227,6 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti unique_lock server_channel_lock(this->server->channel_tree_lock); this->server->unregisterClient(this->ref(), "disconnected", server_channel_lock); } - this->server->group_manager()->disableCache(this->getClientDatabaseId()); //this->server = nullptr; } diff --git a/server/src/groups/Group.h b/server/src/groups/Group.h index aa8d87f..cd256e4 100644 --- a/server/src/groups/Group.h +++ b/server/src/groups/Group.h @@ -22,26 +22,28 @@ namespace ts::server::groups { typedef uint32_t GroupIconId; class AbstractGroupManager; + class Group { friend class AbstractGroupManager; + public: - Group(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + Group(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, + std::shared_ptr /* permissions */); + virtual ~Group() = default; /* information getters */ [[nodiscard]] inline ServerId virtual_server_id() const { return this->virtual_server_id_; } + [[nodiscard]] inline GroupId group_id() const { return this->group_id_; } + [[nodiscard]] inline GroupType group_type() const { return this->type_; } - [[nodiscard]] inline const std::string& display_name() const { return this->name_; } + + [[nodiscard]] inline const std::string &display_name() const { return this->name_; } /* we're not returning a cr here because the permissions might get reloaded */ - [[nodiscard]] inline std::shared_ptr permissions() { return this->permissions_; } - - [[nodiscard]] inline bool save_assignments() const { - assert(this->permissions_); - auto value = this->permissions_->permission_value_flagged(permission::b_group_is_permanent); - return value.has_value ? permission::v2::permission_granted(1, value) : true; - } + [[nodiscard]] inline std::shared_ptr + permissions() { return this->permissions_; } [[nodiscard]] inline GroupNameMode name_mode() const { assert(this->permissions_); @@ -75,17 +77,19 @@ namespace ts::server::groups { return data.has_value ? data.value : 0; } - [[nodiscard]] inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) { + [[nodiscard]] inline bool permission_granted(const permission::PermissionType &permission, + const permission::v2::PermissionFlaggedValue &granted_value, + bool require_granted_value) { auto permission_manager = this->permissions_;; assert(permission_manager); const auto data = permission_manager->permission_value_flagged(permission); - if(!data.has_value) { + if (!data.has_value) { return !require_granted_value || granted_value.has_value; } - if(!granted_value.has_value) { + if (!granted_value.has_value) { return false; } - if(data.value == -1) { + if (data.value == -1) { return granted_value.value == -1; } return granted_value.value >= data.value; @@ -99,17 +103,19 @@ namespace ts::server::groups { std::shared_ptr permissions_; - void set_permissions(const std::shared_ptr& /* permissions */); + void set_permissions(const std::shared_ptr & /* permissions */); }; class ServerGroup : public Group { public: - ServerGroup(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + ServerGroup(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, + std::shared_ptr /* permissions */); }; class ChannelGroup : public Group { public: - ChannelGroup(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + ChannelGroup(ServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, + std::shared_ptr /* permissions */); }; } DEFINE_TRANSFORMS(ts::server::groups::GroupType, uint8_t); diff --git a/server/src/groups/GroupAssignmentManager.cpp b/server/src/groups/GroupAssignmentManager.cpp index 4658f2a..96bf285 100644 --- a/server/src/groups/GroupAssignmentManager.cpp +++ b/server/src/groups/GroupAssignmentManager.cpp @@ -8,7 +8,30 @@ using namespace ts::server::groups; -GroupAssignmentManager::GroupAssignmentManager(GroupManager* handle) : manager_{handle} { } +namespace ts::server::groups { + struct InternalChannelGroupAssignment { + InternalChannelGroupAssignment(ChannelId channel_id, GroupId group_id, bool t) : channel_id{channel_id}, group_id{group_id}, temporary_assignment{t} { } + InternalChannelGroupAssignment(const InternalChannelGroupAssignment& other) = default; + InternalChannelGroupAssignment(InternalChannelGroupAssignment&&) = default; + InternalChannelGroupAssignment&operator=(const InternalChannelGroupAssignment&) = default; + + ChannelId channel_id; + GroupId group_id; + bool temporary_assignment; + }; + + struct InternalServerGroupAssignment { + explicit InternalServerGroupAssignment(GroupId group_id, bool temporary) : group_id{group_id}, temporary_assignment{temporary} { } + InternalServerGroupAssignment(const InternalServerGroupAssignment& other) = default; + InternalServerGroupAssignment(InternalServerGroupAssignment&&) = default; + InternalServerGroupAssignment&operator=(const InternalServerGroupAssignment&) = default; + + GroupId group_id; + bool temporary_assignment; + }; +} + +GroupAssignmentManager::GroupAssignmentManager(GroupManager* handle) : manager_{handle}, client_cache_lock{std::make_shared()} { } GroupAssignmentManager::~GroupAssignmentManager() = default; bool GroupAssignmentManager::initialize(std::string &error) { @@ -25,8 +48,8 @@ ts::ServerId GroupAssignmentManager::server_id() { bool GroupAssignmentManager::load_data(std::string &error) { if constexpr(kCacheAllClients) { - std::lock_guard cache_lock{this->client_cache_lock}; - std::unique_ptr current_entry{nullptr}; + std::lock_guard cache_lock{*this->client_cache_lock}; + std::shared_ptr current_entry{nullptr}; auto res = sql::command(this->sql_manager(), "SELECT `groupId`, `cldbid`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid ORDER BY `cldbid`", variable{":sid", this->server_id()}) .query([&](int length, std::string* value, std::string* column) { @@ -55,14 +78,15 @@ bool GroupAssignmentManager::load_data(std::string &error) { this->client_cache.push_back(std::move(current_entry)); if(!current_entry) { - current_entry = std::make_unique(); + current_entry = std::make_shared(); current_entry->client_database_id = client_dbid; } - if(channel_id) - current_entry->channel_group_assignments.emplace_back(channel_id, group_id, false); - else - current_entry->server_group_assignments.emplace_back(group_id); + if(channel_id) { + current_entry->channel_group_assignments.push_back(std::make_unique(channel_id, group_id, false)); + } else { + current_entry->server_group_assignments.push_back(std::make_unique(group_id, false)); + } return 0; }); @@ -79,7 +103,7 @@ bool GroupAssignmentManager::load_data(std::string &error) { } void GroupAssignmentManager::unload_data() { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; this->client_cache.clear(); } @@ -87,7 +111,7 @@ void GroupAssignmentManager::enable_cache_for_client(GroupAssignmentCalculateMod if constexpr(!kCacheAllClients) { bool cache_exists{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& client : this->client_cache) if(client->client_database_id == cldbid) { client->use_count++; @@ -97,7 +121,7 @@ void GroupAssignmentManager::enable_cache_for_client(GroupAssignmentCalculateMod } if(!cache_exists) { - auto cache = std::make_unique(); + auto cache = std::make_shared(); cache->client_database_id = cldbid; cache->use_count++; @@ -122,14 +146,15 @@ void GroupAssignmentManager::enable_cache_for_client(GroupAssignmentCalculateMod if(!group_id) return 0; - if(channel_id) - cache->channel_group_assignments.emplace_back(channel_id, group_id, false); - else - cache->server_group_assignments.emplace_back(group_id); + if(channel_id) { + cache->channel_group_assignments.push_back(std::make_unique(channel_id, group_id, false)); + } else { + cache->server_group_assignments.push_back(std::make_unique(group_id, false)); + } return 0; }); - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; #if 0 /* lets have some performance over double entries :D */ for(auto& client : this->client_cache) if(client->client_database_id == cldbid) { @@ -152,8 +177,8 @@ void GroupAssignmentManager::enable_cache_for_client(GroupAssignmentCalculateMod void GroupAssignmentManager::disable_cache_for_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) { if constexpr(!kCacheAllClients) { - std::lock_guard cache_lock{this->client_cache_lock}; - this->client_cache.erase(std::remove_if(this->client_cache.begin(), this->client_cache.end(), [cldbid](const std::unique_ptr& client) { + std::lock_guard cache_lock{*this->client_cache_lock}; + this->client_cache.erase(std::remove_if(this->client_cache.begin(), this->client_cache.end(), [cldbid](const std::shared_ptr& client) { return client->client_database_id == cldbid; }), this->client_cache.end()); } @@ -169,13 +194,13 @@ std::vector GroupAssignmentManager::server_groups_of_client(ts::ser std::vector result{}; bool cache_found{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& entry : this->client_cache) { if(entry->client_database_id != cldbid) continue; result.reserve(entry->server_group_assignments.size()); for(auto& assignment : entry->server_group_assignments) - result.push_back(assignment.group_id); + result.push_back(assignment->group_id); cache_found = true; break; @@ -222,12 +247,18 @@ std::vector GroupAssignmentManager::exact_channel_groups std::vector result{}; bool cache_found{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& entry : this->client_cache) { if(entry->client_database_id != cldbid) continue; result.reserve(entry->channel_group_assignments.size()); - result.insert(result.begin(), entry->channel_group_assignments.begin(), entry->channel_group_assignments.end()); + for(const auto& assignment : entry->channel_group_assignments) { + result.push_back(ChannelGroupAssignment{ + .client_database_id = cldbid, + .channel_id = assignment->channel_id, + .group_id = assignment->group_id, + }); + } cache_found = true; break; } @@ -255,7 +286,12 @@ std::vector GroupAssignmentManager::exact_channel_groups } if(!group_id || !channel_id) return 0; - result.emplace_back(channel_id, group_id, false); + result.push_back(ChannelGroupAssignment{ + .client_database_id = cldbid, + .channel_id = channel_id, + .group_id = group_id, + }); + return 0; }); LOG_SQL_CMD(res); @@ -273,7 +309,7 @@ std::vector GroupAssignmentManager::exact_channel_groups } std::optional GroupAssignmentManager::exact_channel_group_of_client(GroupAssignmentCalculateMode mode, - ClientDbId client_database_id, ChannelId channel_id) { + ClientDbId client_database_id, ChannelId channel_id) { /* TODO: Improve performance by not querying all groups */ auto assignments = this->exact_channel_groups_of_client(mode, client_database_id); for(const auto& assignment : assignments) { @@ -310,44 +346,87 @@ std::optional GroupAssignmentManager::calculate_channel_group_of_cl return std::nullopt; } -std::deque GroupAssignmentManager::server_group_clients(GroupId group_id) { - std::deque result{}; - if constexpr(kCacheAllClients) { - std::lock_guard cache_lock{this->client_cache_lock}; +std::deque GroupAssignmentManager::server_group_clients(GroupId group_id, bool full_info) { + std::deque result{}; + + if(kCacheAllClients && !full_info) { + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& client : this->client_cache) { - auto it = std::find_if(client->server_group_assignments.begin(), client->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) { - return assignment.group_id == group_id; + auto it = std::find_if(client->server_group_assignments.begin(), client->server_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->group_id == group_id; }); if(it == client->server_group_assignments.end()) { continue; } - result.push_back(client->client_database_id); + result.push_back(ServerGroupAssignment{ + .client_database_id = client->client_database_id, + .group_id = group_id, + }); } } else { - auto res = sql::command(this->sql_manager(), "SELECT `cldbid` FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = 0 AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}) - .query([&](int length, std::string* value, std::string* column) { - ClientDbId cldbid{0}; - try { - for(int index = 0; index < length; index++) { - if(column[index] == "cldbid") { - cldbid = std::stoull(value[index]); - break; - } - } - } catch(std::exception& ex) { - logWarning(this->server_id(), "Invalid data found in group assignment table. Failed to parse client database id."); - return 0; - } - if(!cldbid) return 0; + if(full_info) { + constexpr static auto kSqlCommand{ + "SELECT `cldbid`, clients_server.client_unique_id, clients_server.client_nickname FROM `assignedGroups` " + "LEFT JOIN `clients_server` ON `assignedGroups`.`serverId` = `clients_server`.`server_id` AND `assignedGroups`.`cldbid` = `clients_server`.`client_database_id` " + "WHERE `serverId` = :sid AND `channelId` = 0 AND `groupId` = :gid;" + }; - result.push_back(cldbid); - return 0; - }); - LOG_SQL_CMD(res); + std::string sql_command{kSqlCommand}; + auto sql = sql::command{this->sql_manager(), kSqlCommand, variable{":sid", this->server_id()}, variable{":gid", group_id}}; + LOG_SQL_CMD(sql.query([&](int length, std::string* values, std::string* names) { + ServerGroupAssignment assignment{}; + + int index{0}; + try { + assert(names[index] == "cldbid"); + assignment.client_database_id = std::stoull(values[index++]); + + assert(names[index] == "client_unique_id"); + assignment.client_unique_id = values[index++]; + + assert(names[index] == "client_nickname"); + assignment.client_display_name = values[index++]; + + assert(index == length); + } catch (std::exception& ex) { + logError(this->server_id(), "Failed to parse client server group assignment at index {}: {}", + index - 1, + ex.what() + ); + return; + } + + result.push_back(std::move(assignment)); + })); + } else { + auto res = sql::command(this->sql_manager(), "SELECT `cldbid` FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = 0 AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}) + .query([&](int length, std::string* value, std::string* column) { + ClientDbId cldbid{0}; + try { + for(int index = 0; index < length; index++) { + if(column[index] == "cldbid") { + cldbid = std::stoull(value[index]); + break; + } + } + } catch(std::exception& ex) { + logWarning(this->server_id(), "Invalid data found in group assignment table. Failed to parse client database id."); + return 0; + } + if(!cldbid) return 0; + + result.push_back(ServerGroupAssignment{ + .client_database_id = cldbid, + .group_id = group_id, + }); + return 0; + }); + LOG_SQL_CMD(res); + } } if(auto parent = this->manager_->parent_manager(); parent) { - auto parent_clients = parent->assignments().server_group_clients(group_id); + auto parent_clients = parent->assignments().server_group_clients(group_id, full_info); result.insert(result.begin(), parent_clients.begin(), parent_clients.end()); } @@ -407,60 +486,62 @@ std::deque> GroupAssignme return result; } -GroupAssignmentResult GroupAssignmentManager::add_server_group(ClientDbId client, GroupId group) { - bool cache_verified{false}; +GroupAssignmentResult GroupAssignmentManager::add_server_group(ClientDbId client, GroupId group, bool temporary) { + bool cache_registered{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& entry : this->client_cache) { if(entry->client_database_id != client) continue; - auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) { - return assignment.group_id == group; + auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->group_id == group; }); + if(it != entry->server_group_assignments.end()) { return GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP; } - entry->server_group_assignments.emplace_back(group); - cache_verified = true; + + entry->server_group_assignments.push_back(std::make_unique(group, temporary)); + cache_registered = true; break; } - if(!cache_verified && kCacheAllClients) { + if(!cache_registered && kCacheAllClients) { /* add the client to the cache */ - auto cache = std::make_unique(); + auto cache = std::make_shared(); cache->client_database_id = client; - cache->server_group_assignments.emplace_back(group); + cache->server_group_assignments.push_back(std::make_unique(group, temporary)); this->client_cache.push_back(std::move(cache)); } } - { - + if(!temporary) { auto command = sql::command(this->sql_manager(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", variable{":sid", this->server_id()}, variable{":cldbid", client}, variable{":gid", group}, variable{":chid", 0}, variable{":until", 0}); - if(cache_verified) + if(cache_registered) { command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to insert group assignment into the database"}); - else { + } else { auto result = command.execute(); if(!result) return GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP; //TODO: Parse error from database? } } + return GroupAssignmentResult::SUCCESS; } GroupAssignmentResult GroupAssignmentManager::remove_server_group(ClientDbId client, GroupId group) { bool cache_verified{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& entry : this->client_cache) { if(entry->client_database_id != client) continue; - auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) { - return assignment.group_id == group; + auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->group_id == group; }); if(it == entry->server_group_assignments.end()) { return GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP; @@ -495,24 +576,24 @@ GroupAssignmentResult GroupAssignmentManager::remove_server_group(ClientDbId cli GroupAssignmentResult GroupAssignmentManager::set_channel_group(ClientDbId client, GroupId group, ChannelId channel_id, bool temporary) { bool cache_verified{false}; { - std::lock_guard cache_lock{this->client_cache_lock}; + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& entry : this->client_cache) { if(entry->client_database_id != client) continue; - auto it = std::find_if(entry->channel_group_assignments.begin(), entry->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) { - return assignment.channel_id == channel_id; + auto it = std::find_if(entry->channel_group_assignments.begin(), entry->channel_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->channel_id == channel_id; }); if(it != entry->channel_group_assignments.end()) { if(group) { - if(it->group_id == group) + if((*it)->group_id == group) return GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP; - it->group_id = group; + (*it)->group_id = group; } else { entry->channel_group_assignments.erase(it); } } else { if(group) { - entry->channel_group_assignments.emplace_back(channel_id, group, temporary); + entry->channel_group_assignments.emplace_back(std::make_unique(channel_id, group, temporary)); } } cache_verified = true; @@ -522,9 +603,9 @@ GroupAssignmentResult GroupAssignmentManager::set_channel_group(ClientDbId clien if(!cache_verified && kCacheAllClients) { if(group) { /* add the client to the cache */ - auto cache = std::make_unique(); + auto cache = std::make_shared(); cache->client_database_id = client; - cache->channel_group_assignments.emplace_back(channel_id, group, temporary); + cache->channel_group_assignments.emplace_back(std::make_unique(channel_id, group, temporary)); this->client_cache.push_back(std::move(cache)); } else { return GroupAssignmentResult::SUCCESS; @@ -554,47 +635,137 @@ GroupAssignmentResult GroupAssignmentManager::set_channel_group(ClientDbId clien return GroupAssignmentResult::SUCCESS; } -void GroupAssignmentManager::cleanup_channel_assignments(ChannelId channel_id) { - { - std::lock_guard cache_lock{this->client_cache_lock}; - for(auto& client : this->client_cache) { - client->channel_group_assignments.erase(std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) { - return assignment.channel_id == channel_id; - })); - } - } - - sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = :cid", variable{":sid", this->server_id()}, variable{":cid", channel_id}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); -} - -void GroupAssignmentManager::cleanup_assignments() { - { - - std::lock_guard cache_lock{this->client_cache_lock}; - this->client_cache.clear(); - } - - sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->server_id()}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); -} - -void GroupAssignmentManager::cleanup_channel_temporary_assignment(ClientDbId client_dbid, ChannelId channel) { - std::lock_guard cache_lock{this->client_cache_lock}; +void GroupAssignmentManager::cleanup_temporary_channel_assignment(ClientDbId client_dbid, ChannelId channel) { + std::lock_guard cache_lock{*this->client_cache_lock}; for(auto& client : this->client_cache) { if(client->client_database_id == client_dbid) { - auto assignment = std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) { - return assignment.channel_id == channel; + auto assignment = std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->channel_id == channel; }); if(assignment == client->channel_group_assignments.end()) { break; } - if(assignment->temporary_assignment) { + if((*assignment)->temporary_assignment) { client->channel_group_assignments.erase(assignment); } break; } } +} + +bool GroupAssignmentManager::is_server_group_empty(GroupId group_id) { + bool result{true}; + if(kCacheAllClients) { + std::lock_guard cache_lock{*this->client_cache_lock}; + for(auto& entry : this->client_cache) { + for(auto& assignment : entry->server_group_assignments) { + if(assignment->group_id == group_id) { + return false; + } + } + } + } else { + auto sql = sql::command{this->sql_manager(), "SELECT COUNT(*) FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}}; + LOG_SQL_CMD(sql.query([&](int, std::string* values, std::string*) { + result = std::stoul(values[0]) == 0; + })); + } + return result; +} + +bool GroupAssignmentManager::is_channel_group_empty(GroupId group_id) { + bool result{true}; + if(kCacheAllClients) { + std::lock_guard cache_lock{*this->client_cache_lock}; + for(auto& entry : this->client_cache) { + for(auto& assignment : entry->channel_group_assignments) { + if(assignment->group_id == group_id) { + return false; + } + } + } + } else { + auto sql = sql::command{this->sql_manager(), "SELECT COUNT(*) FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}}; + LOG_SQL_CMD(sql.query([&](int, std::string* values, std::string*) { + result = std::stoul(values[0]) == 0; + })); + } + return result; +} + +void GroupAssignmentManager::handle_channel_deleted(ChannelId channel_id) { + auto sql = sql::command{this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = :cid", variable{":sid", this->server_id()}, variable{":cid", channel_id}}; + sql.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete assignments for deleted channel"}); + + std::lock_guard cache_lock{*this->client_cache_lock}; + for(auto& entry : this->client_cache) { + entry->channel_group_assignments.erase(std::remove_if(entry->channel_group_assignments.begin(), entry->channel_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->channel_id == channel_id; + }), entry->channel_group_assignments.end()); + } +} + +void GroupAssignmentManager::handle_server_group_deleted(GroupId group_id) { + auto sql = sql::command{this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}}; + sql.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete assignments for deleted server group"}); + + std::lock_guard cache_lock{*this->client_cache_lock}; + for(auto& entry : this->client_cache) { + entry->server_group_assignments.erase(std::remove_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->group_id == group_id; + }), entry->server_group_assignments.end()); + } +} + +void GroupAssignmentManager::handle_channel_group_deleted(GroupId group_id) { + auto sql = sql::command{this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id}}; + sql.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete assignments for deleted channel group"}); + + std::lock_guard cache_lock{*this->client_cache_lock}; + for(auto& entry : this->client_cache) { + entry->channel_group_assignments.erase(std::remove_if(entry->channel_group_assignments.begin(), entry->channel_group_assignments.end(), [&](const std::unique_ptr& assignment) { + return assignment->group_id == group_id; + }), entry->channel_group_assignments.end()); + } +} + +void GroupAssignmentManager::reset_all() { + auto sql = sql::command{this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->server_id()}}; + sql.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete all assignments"}); +} + +std::shared_ptr GroupAssignmentManager::create_tmp_assignment_lock(ClientDbId cldbid) { + std::shared_ptr cache{}; + + std::lock_guard cache_lock{*this->client_cache_lock}; + for(const auto& entry : this->client_cache) { + if(entry->client_database_id == cldbid) { + cache = entry; + break; + } + } + if(!cache) { + cache = std::make_shared(); + cache->client_database_id = cldbid; + this->client_cache.push_back(cache); + } + + auto cache_mutex = this->client_cache_lock; + std::shared_ptr temp_assignment_lock{new char{}, [cache, cache_mutex](void* buffer) { + delete (char*) buffer; + + std::lock_guard cache_lock{*cache_mutex}; + cache->server_group_assignments.erase(std::remove_if(cache->server_group_assignments.begin(), cache->server_group_assignments.end(), [](const std::unique_ptr& assignment){ + return assignment->temporary_assignment; + }), cache->server_group_assignments.end()); + + cache->channel_group_assignments.erase(std::remove_if(cache->channel_group_assignments.begin(), cache->channel_group_assignments.end(), [](const std::unique_ptr& assignment){ + return assignment->temporary_assignment; + }), cache->channel_group_assignments.end()); + }}; + + cache->temp_assignment_lock = temp_assignment_lock; + return temp_assignment_lock; } \ No newline at end of file diff --git a/server/src/groups/GroupAssignmentManager.h b/server/src/groups/GroupAssignmentManager.h index 9ef2462..3baab92 100644 --- a/server/src/groups/GroupAssignmentManager.h +++ b/server/src/groups/GroupAssignmentManager.h @@ -31,25 +31,25 @@ namespace ts::server { class GroupManager; struct ChannelGroupAssignment { - ChannelGroupAssignment(ChannelId channel_id, GroupId group_id, bool t) : channel_id{channel_id}, group_id{group_id}, temporary_assignment{t} { } - ChannelGroupAssignment(const ChannelGroupAssignment& other) = default; - ChannelGroupAssignment(ChannelGroupAssignment&&) = default; - ChannelGroupAssignment&operator=(const ChannelGroupAssignment&) = default; - + ClientDbId client_database_id; ChannelId channel_id; GroupId group_id; - bool temporary_assignment; }; struct ServerGroupAssignment { - explicit ServerGroupAssignment(GroupId group_id) : group_id{group_id} { } - ServerGroupAssignment(const ServerGroupAssignment& other) = default; - ServerGroupAssignment(ServerGroupAssignment&&) = default; - ServerGroupAssignment&operator=(const ServerGroupAssignment&) = default; + ClientDbId client_database_id{0}; + GroupId group_id{0}; - GroupId group_id; + /* both fields will only be set on extended info */ + std::optional client_display_name{}; + std::optional client_unique_id{}; }; + typedef void TemporaryAssignmentsLock; + + struct InternalChannelGroupAssignment; + struct InternalServerGroupAssignment; + enum struct GroupAssignmentResult { SUCCESS, ADD_ALREADY_MEMBER_OF_GROUP, @@ -82,43 +82,44 @@ namespace ts::server { /** * 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. + * Note: `target channel` will be altered if the result 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 server_group_clients(GroupId /* group id */, bool /* full info */); [[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 */); + GroupAssignmentResult add_server_group(ClientDbId /* client database id */, GroupId /* group id */, bool /* temporary assignment */); GroupAssignmentResult remove_server_group(ClientDbId /* client database id */, GroupId /* group id */); GroupAssignmentResult set_channel_group(ClientDbId /* client database id */, GroupId /* group id */, ChannelId /* channel id */, bool /* temporary assignment */); - void cleanup_assignments(); - void cleanup_channel_assignments(ChannelId /* channel */); - void cleanup_channel_temporary_assignment(ClientDbId /* client database id */, ChannelId /* channel */); + [[nodiscard]] std::shared_ptr create_tmp_assignment_lock(ClientDbId /* client database id */); + void cleanup_temporary_channel_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: + GroupManager* manager_; + struct ClientCache { ClientDbId client_database_id{0}; size_t use_count{0}; - std::deque channel_group_assignments{}; - std::deque server_group_assignments{}; + std::deque> channel_group_assignments{}; + std::deque> server_group_assignments{}; + + std::weak_ptr temp_assignment_lock{}; }; - GroupManager* manager_; - - std::mutex client_cache_lock; - std::deque> client_cache{}; + std::shared_ptr client_cache_lock{}; + std::deque> client_cache{}; [[nodiscard]] sql::SqlManager* sql_manager(); diff --git a/server/src/groups/GroupManager.cpp b/server/src/groups/GroupManager.cpp index a3b6930..9744b11 100644 --- a/server/src/groups/GroupManager.cpp +++ b/server/src/groups/GroupManager.cpp @@ -51,11 +51,13 @@ void GroupManager::save_permissions() { auto time_server_groups = std::chrono::duration_cast(timestamp_1 - timestamp_0).count(); auto time_channel_groups = std::chrono::duration_cast(timestamp_2 - timestamp_1).count(); - debugMessage(this->server_id(), "Saved {}/{} server and {}/{} channel group permissions in {}ms or {}ms", - saved_groups_server, total_groups_server, - saved_groups_channel, total_groups_channel, - time_server_groups, time_channel_groups - ); + if(saved_groups_channel > 0 || saved_groups_channel > 0) { + debugMessage(this->server_id(), "Saved {}/{} server and {}/{} channel group permissions in {}ms or {}ms", + saved_groups_server, total_groups_server, + saved_groups_channel, total_groups_channel, + time_server_groups, time_channel_groups + ); + } } /* Abstract group manager */ @@ -114,47 +116,60 @@ void AbstractGroupManager::unload_data() { } } -void AbstractGroupManager::reset_groups(const std::shared_ptr &template_provider, std::map &mapping) { +void AbstractGroupManager::reset_groups(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", + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` IN (SELECT `groupId` FROM `groups` WHERE `serverId` = :serverId AND AND `target` = :target)", 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()); + variable{":type", ts::permission::SQL_PERM_GROUP}, + variable{":target", (uint8_t) this->database_target_}).execute()); + + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId AND `groupId` IN (SELECT `groupId` FROM `groups` WHERE `serverId` = :serverId AND AND `target` = :target)", + variable{":serverId", this->server_id()}, + variable{":target", (uint8_t) this->database_target_}).execute()); + + LOG_SQL_CMD(sql::command(this->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId AND `target` = :target", + variable{":serverId", this->server_id()}, + variable{":target", (uint8_t) this->database_target_} + ).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(); + if(this->parent_manager_) { + for(const auto& group : this->parent_manager_->groups_) { + if(group->group_type() != GroupType::GROUP_TYPE_TEMPLATE) { + continue; + } - if(db_cleanup) { - /* 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()); - } + //GroupCopyResult + std::shared_ptr created_group{}; + auto result = this->copy_group_(group->group_id(), GroupType::GROUP_TYPE_NORMAL, group->display_name(), created_group); + switch(result) { + case GroupCopyResult::SUCCESS: + break; - 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); + case GroupCopyResult::NAME_INVALID: + case GroupCopyResult::NAME_ALREADY_IN_USE: + case GroupCopyResult::UNKNOWN_TARGET_GROUP: + case GroupCopyResult::UNKNOWN_SOURCE_GROUP: + case GroupCopyResult::DATABASE_ERROR: + logCritical(this->server_id(), "Failed to copy template group {}: {}", group->group_id(), (uint8_t) result); + break; + } + + logTrace(this->server_id(), "Copied template group {}. New id: {}", group->group_id(), created_group->group_id()); + mapping[group->group_id()] = created_group->group_id(); + } } } + int AbstractGroupManager::insert_group_from_sql(int length, std::string *values, std::string *names) { GroupId group_id{0}; GroupType group_type{GroupType::GROUP_TYPE_UNKNOWN}; @@ -169,7 +184,7 @@ int AbstractGroupManager::insert_group_from_sql(int length, std::string *values, } else if(names[index] == "type") { group_type = (GroupType) std::stoull(values[index]); } else if(names[index] == "displayName") { - group_name = names[index]; + group_name = values[index]; } } catch(std::exception& ex) { logWarning(this->server_id(), "Failed to parse group from database. Failed to parse column {} (value: {})", names[index], values[index]); @@ -190,20 +205,24 @@ int AbstractGroupManager::insert_group_from_sql(int length, std::string *values, return 0; } -void AbstractGroupManager::save_permissions() { - std::unique_lock group_lock{this->groups_}; +void AbstractGroupManager::save_permissions(size_t &total_groups, size_t &saved_groups) { + std::unique_lock group_lock{this->group_mutex_}; auto groups = this->groups_; group_lock.unlock(); + total_groups = groups.size(); + saved_groups = 0; + for(auto& group : groups) { auto permissions = group->permissions(); if(permissions->require_db_updates()) { - serverInstance->databaseHelper()->saveGroupPermissions(0, group->group_id(), (uint8_t) this->database_target_, permissions); + serverInstance->databaseHelper()->saveGroupPermissions(this->server_id(), group->group_id(), (uint8_t) this->database_target_, permissions); + saved_groups++; } } } -std::shared_ptr AbstractGroupManager::find_group_(GroupCalculateMode mode, GroupId group_id) { +std::shared_ptr AbstractGroupManager::find_group_(std::shared_ptr& owning_manager, GroupCalculateMode mode, GroupId group_id) { { std::lock_guard glock{this->group_mutex_}; auto it = std::find_if(this->groups_.begin(), this->groups_.end(), [&](const std::shared_ptr& group) { @@ -211,12 +230,16 @@ std::shared_ptr AbstractGroupManager::find_group_(GroupCalculateMode mode }); if(it != this->groups_.end()) { + owning_manager = this->shared_from_this(); return *it; } } + if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_) { - return this->parent_manager_->find_group_(mode, group_id); + owning_manager = this->parent_manager_; + return this->parent_manager_->find_group_(owning_manager, mode, group_id); } + return nullptr; } @@ -269,7 +292,8 @@ GroupCreateResult AbstractGroupManager::create_group_(GroupType type, const std: auto group_id = res.last_insert_rowid(); auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, (uint8_t) this->database_target_); - auto group = std::make_shared(this->server_id(), group_id, type, name, permissions); + auto group = this->allocate_group(group_id, type, name, permissions); + { std::lock_guard glock{this->group_mutex_}; this->groups_.push_back(group); @@ -291,6 +315,10 @@ GroupCopyResult AbstractGroupManager::copy_group_(GroupId source, GroupType targ case GroupCreateResult::NAME_ALREADY_IN_USED: return GroupCopyResult::NAME_ALREADY_IN_USE; + + case GroupCreateResult::NAME_TOO_LONG: + case GroupCreateResult::NAME_TOO_SHORT: + return GroupCopyResult::NAME_INVALID; } assert(result); @@ -300,35 +328,49 @@ GroupCopyResult AbstractGroupManager::copy_group_(GroupId source, GroupType targ GroupCopyResult AbstractGroupManager::copy_group_permissions_(GroupId source, GroupId target) { std::lock_guard manage_lock{this->group_manage_mutex_}; - auto source_group = this->find_group_(groups::GroupCalculateMode::GLOBAL, source); + std::shared_ptr owning_manager{}; + auto source_group = this->find_group_(owning_manager, groups::GroupCalculateMode::GLOBAL, source); if(!source_group) { return GroupCopyResult::UNKNOWN_SOURCE_GROUP; } - auto target_group = this->find_group_(groups::GroupCalculateMode::GLOBAL, target); + auto target_group = this->find_group_(owning_manager, groups::GroupCalculateMode::GLOBAL, target); if(!target_group) { return GroupCopyResult::UNKNOWN_TARGET_GROUP; } - auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", - variable{":sid", this->server_id()}, - variable{":type", ts::permission::SQL_PERM_GROUP}, - variable{":id", target}).execute(); - if(!res) { - LOG_SQL_CMD(res); - return GroupCopyResult::DATABASE_ERROR; + if(target_group->permissions()->require_db_updates()) { + /* TODO: Somehow flush all pending changes */ } - res = sql::command(this->sql_manager(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", - variable{":ssid", source_group->virtual_server_id()}, - variable{":tsid", target_group->virtual_server_id()}, - variable{":type", ts::permission::SQL_PERM_GROUP}, - variable{":source", source}, - variable{":target", target}).execute(); + { + auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", + variable{":sid", this->server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}, + variable{":id", target}).execute(); + if(!res) { + LOG_SQL_CMD(res); + return GroupCopyResult::DATABASE_ERROR; + } + } + + { + constexpr static auto kSqlCommand = "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) " + "SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source"; + auto res = sql::command(this->sql_manager(), kSqlCommand, + variable{":ssid", source_group->virtual_server_id()}, + variable{":tsid", target_group->virtual_server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}, + variable{":source", source}, + variable{":target", target}).execute(); + if(!res) { + + LOG_SQL_CMD(res); + return GroupCopyResult::DATABASE_ERROR; + } + } target_group->set_permissions(serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), target, (uint8_t) this->database_target_)); - LOG_SQL_CMD(res); - return GroupCopyResult::SUCCESS; } @@ -338,7 +380,8 @@ GroupRenameResult AbstractGroupManager::rename_group_(GroupId group_id, const st return GroupRenameResult::NAME_INVALID; } - auto group = this->find_group_(groups::GroupCalculateMode::GLOBAL, group_id); + std::shared_ptr owning_manager{}; + auto group = this->find_group_(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { return GroupRenameResult::INVALID_GROUP_ID; } @@ -360,7 +403,7 @@ GroupDeleteResult AbstractGroupManager::delete_group_(GroupId group_id) { { std::unique_lock glock{this->group_mutex_}; - auto it = std::find_if(this->groups_.begin(), this->groups_.begin(), [&](const std::shared_ptr& group) { + auto it = std::find_if(this->groups_.begin(), this->groups_.end(), [&](const std::shared_ptr& group) { return group->group_id() == group_id; }); @@ -376,17 +419,13 @@ GroupDeleteResult AbstractGroupManager::delete_group_(GroupId group_id) { this->groups_.erase(it); } - sql::command(this->sql_manager(), "DELETE FROM WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", + sql::command(this->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", variable{":server", this->server_id()}, variable{":target", (uint8_t) this->database_target_}, variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"}); 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) : AbstractGroupManager{handle->sql_manager(), DatabaseGroupTarget::SERVER, handle->server_id(), parent} @@ -401,7 +440,7 @@ std::shared_ptr ServerGroupManager::allocate_group(GroupId id, GroupType /* Channel group manager */ ChannelGroupManager::ChannelGroupManager(const std::shared_ptr &handle, std::shared_ptr parent) - : AbstractGroupManager{handle->sql_manager(), DatabaseGroupTarget::SERVER, handle->server_id(), parent} + : AbstractGroupManager{handle->sql_manager(), DatabaseGroupTarget::CHANNEL, handle->server_id(), parent} { } diff --git a/server/src/groups/GroupManager.h b/server/src/groups/GroupManager.h index bcbcb76..8cbf920 100644 --- a/server/src/groups/GroupManager.h +++ b/server/src/groups/GroupManager.h @@ -34,6 +34,7 @@ namespace ts::server::groups { UNKNOWN_SOURCE_GROUP, UNKNOWN_TARGET_GROUP, NAME_ALREADY_IN_USE, + NAME_INVALID, DATABASE_ERROR }; @@ -48,11 +49,13 @@ namespace ts::server::groups { enum struct GroupDeleteResult { SUCCESS, INVALID_GROUP_ID, + /* Artificial result, not used by the delete method but needed */ + GROUP_NOT_EMPTY, DATABASE_ERROR }; class GroupManager; - class AbstractGroupManager { + class AbstractGroupManager : public std::enable_shared_from_this { friend class Group; friend class GroupAssignmentManager; public: @@ -77,10 +80,9 @@ namespace ts::server::groups { 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. + * Reset all known groups and copy the template groups from our group parent (if it isn't null) */ - void reset_groups(const std::shared_ptr& /* template group provider */, std::map& /* mapping */); + void reset_groups(std::map& /* mapping */); protected: std::shared_ptr parent_manager_; DatabaseGroupTarget database_target_; @@ -98,7 +100,7 @@ namespace ts::server::groups { [[nodiscard]] sql::SqlManager* sql_manager(); [[nodiscard]] ServerId server_id(); - [[nodiscard]] std::shared_ptr find_group_(GroupCalculateMode /* mode */, GroupId /* group id */); + [[nodiscard]] std::shared_ptr find_group_(std::shared_ptr& /* owning manager */, GroupCalculateMode /* mode */, GroupId /* group id */); [[nodiscard]] std::shared_ptr find_group_by_name_(GroupCalculateMode /* mode */, const std::string& /* group name */); [[nodiscard]] GroupCreateResult create_group_(GroupType type, const std::string& /* group name */, std::shared_ptr& /* result */); [[nodiscard]] GroupCopyResult copy_group_(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */, std::shared_ptr& /* result */); @@ -140,7 +142,25 @@ namespace ts::server::groups { } [[nodiscard]] inline std::shared_ptr find_group(GroupCalculateMode mode, GroupId group_id) { - return this->cast_result(this->find_group_(mode, group_id)); + std::shared_ptr owning_manager{}; + return this->cast_result(this->find_group_(owning_manager, mode, group_id)); + } + + /** + * + * @param owning_manager + * @param mode + * @param group_id + * @return the group if found. `owning_manager` will be set to the owning manager. + */ + [[nodiscard]] inline std::shared_ptr find_group_ext(std::shared_ptr& owning_manager, GroupCalculateMode mode, GroupId group_id) { + std::shared_ptr owning_manager_; + auto result = this->cast_result(this->find_group_(owning_manager_, mode, group_id)); + if(owning_manager_) { + owning_manager = std::dynamic_pointer_cast(owning_manager_); + assert(owning_manager); + } + return result; } [[nodiscard]] inline std::shared_ptr find_group_by_name(GroupCalculateMode mode, const std::string& group_name) { @@ -212,7 +232,25 @@ namespace ts::server::groups { } [[nodiscard]] inline std::shared_ptr find_group(GroupCalculateMode mode, GroupId group_id) { - return this->cast_result(this->find_group_(mode, group_id)); + std::shared_ptr owning_manager{}; + return this->cast_result(this->find_group_(owning_manager, mode, group_id)); + } + + /** + * + * @param owning_manager + * @param mode + * @param group_id + * @return the group if found. `owning_manager` will be set to the owning manager. + */ + [[nodiscard]] inline std::shared_ptr find_group_ext(std::shared_ptr& owning_manager, GroupCalculateMode mode, GroupId group_id) { + std::shared_ptr owning_manager_; + auto result = this->cast_result(this->find_group_(owning_manager_, mode, group_id)); + if(owning_manager_) { + owning_manager = std::dynamic_pointer_cast(owning_manager_); + assert(owning_manager); + } + return result; } [[nodiscard]] inline std::shared_ptr find_group_by_name(GroupCalculateMode mode, const std::string& group_name) { diff --git a/server/src/music/MusicBotManager.cpp b/server/src/music/MusicBotManager.cpp index 6a9cceb..b5c3e39 100644 --- a/server/src/music/MusicBotManager.cpp +++ b/server/src/music/MusicBotManager.cpp @@ -96,7 +96,6 @@ std::shared_ptr MusicBotManager::createBot(ClientDbId owner if(!config::music::enabled) return nullptr; - handle->group_manager()->enableCache(musicBot->getClientDatabaseId()); handle->registerClient(musicBot); { @@ -147,7 +146,6 @@ void MusicBotManager::deleteBot(std::shared_ptr musicBot) { handle->client_move(musicBot, nullptr, nullptr, "Music bot deleted", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock); handle->unregisterClient(musicBot, "bot deleted", server_channel_lock); } - handle->group_manager()->disableCache(musicBot->getClientDatabaseId()); serverInstance->databaseHelper()->deleteClient(handle, musicBot->getClientDatabaseId()); serverInstance->databaseHelper()->deleteClient(nullptr, musicBot->getClientDatabaseId()); @@ -241,7 +239,6 @@ int MusicBotManager::sqlCreateMusicBot(int length, std::string* values, std::str musicBot->properties()[property::CLIENT_LASTCONNECTED] = duration_cast(system_clock::now().time_since_epoch()).count(); } - handle->group_manager()->enableCache(musicBot->getClientDatabaseId()); if(musicBot->getClientDatabaseId() != botId) logCritical(handle->getServerId(),"Invalid music bot id mapping!"); { diff --git a/server/src/server/QueryServer.cpp b/server/src/server/QueryServer.cpp index 26ff42d..e80f654 100644 --- a/server/src/server/QueryServer.cpp +++ b/server/src/server/QueryServer.cpp @@ -41,11 +41,6 @@ void QueryServer::unregisterConnection(const shared_ptr &client) { } } - if(client->server) { - client->server->group_manager()->disableCache(client->getClientDatabaseId()); - } else { - serverInstance->group_manager()->disableCache(client->getClientDatabaseId()); - } /* client->handle = nullptr; */ } diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index bc054a0..670d0cd 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -13,6 +13,7 @@ #include "../InstanceHandler.h" #include "../ShutdownHelper.h" #include "../server/QueryServer.h" +#include "../groups/GroupManager.h" #ifdef HAVE_JEMALLOC #include @@ -293,7 +294,7 @@ namespace terminal::chandler { return false; } - auto group = server->group_manager()->findGroup(groupId); + auto group = server->group_manager()->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, groupId); if(!group) { handle.response.emplace_back("Could not resolve server group!"); return false; diff --git a/server/things_to_test b/server/things_to_test index 9bc9ca5..6568313 100644 --- a/server/things_to_test +++ b/server/things_to_test @@ -8,6 +8,16 @@ general server & channel groups Notified channel & server member add/remove powers Group edit if the notify group triggers -channelgroupclientlist +Icon IDs seem to be buggy with groups +group permission copy dosn't work +permission reset -TODO: Delete the group header +servergroupclientlist + +server default group edit +channelgroupclientlist +temporary group/channel assignments +Test with `kCacheAllClients` and without `kCacheAllClients` + +TODO: +- Delete the group header \ No newline at end of file