920 lines
41 KiB
C++
920 lines
41 KiB
C++
#include <algorithm>
|
|
#include <utility>
|
|
#include <log/LogUtils.h>
|
|
#include <misc/memtracker.h>
|
|
#include "Group.h"
|
|
#include "VirtualServer.h"
|
|
#include "src/client/ConnectedClient.h"
|
|
#include "InstanceHandler.h"
|
|
#include "src/server/file/FileServer.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<Group>(this);
|
|
this->handle = handle;
|
|
|
|
this->_properties = new Properties();
|
|
this->_properties->register_property_type<property::GroupProperties>();
|
|
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
|
|
|
|
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<permission::v2::PermissionManager> &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<Group>(this);
|
|
}
|
|
|
|
GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlManager *sql, std::shared_ptr<GroupManager> root) : server(server), sql(sql), root(std::move(root)) { }
|
|
|
|
GroupManager::~GroupManager() {}
|
|
|
|
bool GroupManager::loadGroupFormDatabase(GroupId id) {
|
|
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<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
|
|
std::vector<std::shared_ptr<Group>> response;
|
|
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<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool root){
|
|
std::vector<std::shared_ptr<Group>> response;
|
|
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<std::shared_ptr<Group>> GroupManager::availableChannelGroups(bool root) {
|
|
std::vector<std::shared_ptr<Group>> response;
|
|
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 if(strcmp(column[index], "serverId") == 0);
|
|
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> group = std::make_shared<Group>(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<property::GroupProperties>(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);
|
|
|
|
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);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
|
|
unique_lock cache_lock(this->cacheLock);
|
|
auto cached_clients = std::vector<shared_ptr<CachedClient>>{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<Group> gr) {
|
|
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
|
|
}
|
|
|
|
std::shared_ptr<Group> 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<GroupId>() :
|
|
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
|
|
auto group = this->findGroupLocal(id);
|
|
if(group || enforce_property) return group;
|
|
|
|
for(auto elm : this->groups)
|
|
if(elm->target() == type)
|
|
return elm;
|
|
|
|
return nullptr; //Worst case!
|
|
}
|
|
|
|
std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
|
|
auto result = this->findGroupLocal(groupId);
|
|
if(!result && this->root) result = this->root->findGroup(groupId);
|
|
return result;
|
|
}
|
|
|
|
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
|
|
for(const auto& elm : this->groups)
|
|
if(elm->groupId() == groupId) return elm;
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<Group>> GroupManager::findGroup(GroupTarget target, std::string name) {
|
|
vector<shared_ptr<Group>> res;
|
|
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<Group> 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> group = std::make_shared<Group>(this, target, type, groupId);
|
|
group->properties()[property::GROUP_NAME] = name;
|
|
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
|
|
this->groups.push_back(group);
|
|
return group;
|
|
}
|
|
|
|
GroupId GroupManager::copyGroup(std::shared_ptr<Group> 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<Group> &source, const shared_ptr<Group> &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> 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> 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> group) {
|
|
if(!isLocalGroup(group)){
|
|
if(this->root) return this->root->deleteGroup(group);
|
|
return false;
|
|
}
|
|
|
|
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<GroupAssignment>& 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<property::ClientProperties> GroupManager::update_server_group_property(const shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel) {
|
|
std::deque<property::ClientProperties> 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<GroupAssignment> 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<CachedClient>();
|
|
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> group = nullptr;
|
|
time_point<system_clock> 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<system_clock>() + 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<GroupAssignment>();
|
|
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<ConnectedClient> 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<CachedClient>& 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;
|
|
}
|
|
|
|
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
|
|
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
|
|
if(!isLocalGroup(group)){
|
|
if(this->root) return this->root->listGroupMembers(group, names);
|
|
return {};
|
|
}
|
|
ResList result;
|
|
|
|
sql::command(this->sql,
|
|
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
|
|
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
|
|
.query([&](ResList* list, int columnCount, char** values, char** columnName){
|
|
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
|
|
member->displayName = "undefined";
|
|
member->uid = "undefined";
|
|
for(int index = 0; index < columnCount; index++){
|
|
if(values[index] == nullptr) {
|
|
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
|
|
continue;
|
|
}
|
|
if(strcmp(columnName[index], "cldbid") == 0)
|
|
member->cldbId = stoll(values[index]);
|
|
else if(strcmp(columnName[index], "until") == 0)
|
|
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
|
|
else if(strcmp(columnName[index], "clientUid") == 0)
|
|
member->uid = values[index];
|
|
else if(strcmp(columnName[index], "lastName") == 0)
|
|
member->displayName = values[index];
|
|
else if(strcmp(columnName[index], "channelId") == 0)
|
|
member->channelId = stoll(values[index]);
|
|
else cerr << "Invalid column name " << columnName[index] << endl;
|
|
}
|
|
list->push_back(member);
|
|
return 0;
|
|
}, &result);
|
|
return result;
|
|
}
|
|
|
|
vector<shared_ptr<GroupAssignment>> GroupManager::listGroupAssignments(ClientDbId cldbId) {
|
|
vector<std::shared_ptr<GroupAssignment>> 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> group = nullptr;
|
|
time_point<system_clock> 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<system_clock>() + 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<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
|
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<CachedClient> 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<std::shared_ptr<GroupAssignment>> GroupManager::getAssignedServerGroups(ClientDbId cldbid) {
|
|
auto cached = this->resolve_cached_client(cldbid);
|
|
sql::result res;
|
|
std::vector<std::shared_ptr<GroupAssignment>> 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> group = nullptr;
|
|
time_point<system_clock> 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<system_clock>() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index]));
|
|
} else cerr << "Invalid column " << column[index] << endl;
|
|
}
|
|
if(!group)
|
|
return 0;
|
|
|
|
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
|
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<std::shared_ptr<GroupAssignment>> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) {
|
|
auto result = this->getAssignedServerGroups(cldbid);
|
|
if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type);
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) {
|
|
std::vector<std::shared_ptr<GroupAssignment>> 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<GroupId>() :
|
|
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>();
|
|
auto group = this->findGroupLocal(id);
|
|
if(group) {
|
|
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
|
|
return result;
|
|
}
|
|
}
|
|
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point<system_clock>()));
|
|
return result;
|
|
}
|
|
|
|
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>& 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<GroupAssignment> 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> group = nullptr;
|
|
time_point<system_clock> 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<system_clock>() + milliseconds(stoll(value[index]));
|
|
} else cerr << "Invalid column " << column[index] << endl;
|
|
}
|
|
if(!group)
|
|
return 0;
|
|
|
|
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
|
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<GroupAssignment> GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr<BasicChannel> &channel, bool assign_default) {
|
|
shared_ptr<GroupAssignment> group;
|
|
std::shared_ptr<BasicChannel> 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<GroupAssignment> GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &channel) {
|
|
return std::make_shared<GroupAssignment>(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point<system_clock>());
|
|
}
|
|
|
|
void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group, time_point<system_clock> 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<GroupAssignment>(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<milliseconds>(until).time_since_epoch().count()})
|
|
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
|
}
|
|
|
|
void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group> 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<GroupAssignment>& 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> group, std::shared_ptr<BasicChannel> channel, time_point<system_clock> 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<GroupAssignment>(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<milliseconds>(until).time_since_epoch().count()})
|
|
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
|
}
|
|
} |