#include #include #include #include #include "Group.h" #include "VirtualServer.h" #include "src/client/ConnectedClient.h" #include "InstanceHandler.h" using namespace std; 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 = new Properties(); 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() { delete this->_properties; 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->server.lock(), group->groupId())); 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_save() : serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save(); 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->server.lock(), group->groupId())); 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->server.lock(), target->groupId())); 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->server.lock(), group->groupId())); 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; flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), 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_save() : serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save(); 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"}); } }