mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
[Server] Gate room channels on privilege levels (#7477)
* [Server] Gate room channels on privilege levels * [Server] Filter channel catalog per user, split read/write gating, add banners - Server_Room::getInfo() now takes the requesting user and only lists channels that user may read. Previously every room member received the full catalog (e.g. the moderators channel) while message delivery was already gated, so restricted channels were visible but silent. - Split channel access into mayReadChannel/mayWriteChannel: history replay, catalog listing, and event fan-out use the read level (access_level); Command_RoomChannelSay uses the write level (write_access_level, falling back to the read level when unset). - Plumb write_access_level and banner_message through both room sources (SQL rooms_channels table and config-file channels via writeaccess and banner keys). - Schema 38 -> 39: add write_access_level (NULL means inherit the read level) and banner_message columns; also widen access_level to the full six-value privilege enum to match the 37 -> 38 migration. * Fixup: evaluate channel access per user flag instead of ladder User flags are independent bits: IsDeveloper does not imply IsModerator/IsAdmin, so developer-only users must not reach moderator/admin channels. Replace the userPrivilegeLevel ladder with hasChannelPrivilege: moderator channels need IsModerator (admins hold it alongside IsAdmin), admin channels need IsAdmin, developer channels need IsDeveloper or IsAdmin, judge channels admit IsJudge/IsModerator. Covers catalog filtering, history replay, write gating and fan-out, and corrects the ini example and proto docs. * [Server] Make channel privilege helpers static per review * [Server] Fail closed on unknown channel access levels per review - roomChannelAccessFromKey returns nullopt for unknown keywords instead of silently falling back to Public - skip channels with unknown read/write levels with qWarning, empty read defaults to Public, empty write means no override - mayWriteChannel now also requires read access so write-below-read configs cannot post into invisible channels * [Server] Remove stray conflict marker in channel replay comment * [Server] Fix servatrice.cpp formatting per clang-format-18 --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: BruebachL <bruebachl@cockatrice.example>
This commit is contained in:
parent
17d2e8d86e
commit
05dcde69db
9 changed files with 211 additions and 29 deletions
|
|
@ -68,6 +68,7 @@
|
|||
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_room_channel.pb.h>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
#include <server_room.h>
|
||||
|
|
@ -255,10 +256,10 @@ Servatrice::~Servatrice()
|
|||
* @brief Maps a configured channel access level (database column or ini value)
|
||||
* onto the ServerInfo_RoomChannel privilege ladder.
|
||||
*
|
||||
* Unknown keywords fall back to Public so a typo never hides a channel that was
|
||||
* meant to be broadly readable.
|
||||
* Returns nullopt for unknown keywords so callers can fail closed instead of
|
||||
* silently downgrading a restricted channel to Public.
|
||||
*/
|
||||
static ServerInfo_RoomChannel::AccessLevel roomChannelAccessFromKey(const QString &key)
|
||||
static std::optional<ServerInfo_RoomChannel::AccessLevel> roomChannelAccessFromKey(const QString &key)
|
||||
{
|
||||
const QString level = key.trimmed().toLower();
|
||||
if (level == "developer") {
|
||||
|
|
@ -276,7 +277,10 @@ static ServerInfo_RoomChannel::AccessLevel roomChannelAccessFromKey(const QStrin
|
|||
if (level == "registered") {
|
||||
return ServerInfo_RoomChannel::Registered;
|
||||
}
|
||||
return ServerInfo_RoomChannel::Public;
|
||||
if (level == "public") {
|
||||
return ServerInfo_RoomChannel::Public;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Servatrice::initServer()
|
||||
|
|
@ -392,7 +396,8 @@ bool Servatrice::initServer()
|
|||
}
|
||||
|
||||
QSqlQuery *query3 = servatriceDatabaseInterface->prepareQuery(
|
||||
"select channel_id, display_name, access_level from {prefix}_rooms_channels where id_room = :id_room "
|
||||
"select channel_id, display_name, access_level, write_access_level, banner_message from "
|
||||
"{prefix}_rooms_channels where id_room = :id_room "
|
||||
"AND id_server = :id_server order by channel_id asc");
|
||||
query3->bindValue(":id_server", serverId);
|
||||
query3->bindValue(":id_room", query->value(0).toInt());
|
||||
|
|
@ -414,10 +419,33 @@ bool Servatrice::initServer()
|
|||
continue;
|
||||
}
|
||||
seenChannelIds.insert(channelId);
|
||||
const QString accessKey = query3->value(2).toString();
|
||||
auto accessLevel =
|
||||
accessKey.trimmed().isEmpty()
|
||||
? std::optional<ServerInfo_RoomChannel::AccessLevel>(ServerInfo_RoomChannel::Public)
|
||||
: roomChannelAccessFromKey(accessKey);
|
||||
if (!accessLevel.has_value()) {
|
||||
qWarning() << "Skipping room channel with unknown access level" << accessKey << "for id"
|
||||
<< channelId;
|
||||
continue;
|
||||
}
|
||||
ServerInfo_RoomChannel channel;
|
||||
channel.set_id(channelId.toStdString());
|
||||
channel.set_display_name(query3->value(1).toString().toStdString());
|
||||
channel.set_access_level(roomChannelAccessFromKey(query3->value(2).toString()));
|
||||
channel.set_access_level(accessLevel.value());
|
||||
if (!query3->isNull(3)) {
|
||||
const QString writeKey = query3->value(3).toString();
|
||||
if (!writeKey.trimmed().isEmpty()) {
|
||||
auto writeLevel = roomChannelAccessFromKey(writeKey);
|
||||
if (!writeLevel.has_value()) {
|
||||
qWarning() << "Skipping room channel with unknown write access level" << writeKey
|
||||
<< "for id" << channelId;
|
||||
continue;
|
||||
}
|
||||
channel.set_write_access_level(writeLevel.value());
|
||||
}
|
||||
}
|
||||
channel.set_banner_message(query3->value(4).toString().toStdString());
|
||||
channels.append(channel);
|
||||
}
|
||||
|
||||
|
|
@ -456,10 +484,33 @@ bool Servatrice::initServer()
|
|||
continue;
|
||||
}
|
||||
seenChannelIds.insert(channelId);
|
||||
const QString accessKey = settingsCache->value("access").toString();
|
||||
auto accessLevel =
|
||||
accessKey.trimmed().isEmpty()
|
||||
? std::optional<ServerInfo_RoomChannel::AccessLevel>(ServerInfo_RoomChannel::Public)
|
||||
: roomChannelAccessFromKey(accessKey);
|
||||
if (!accessLevel.has_value()) {
|
||||
qWarning() << "Skipping room channel with unknown access level" << accessKey << "for id"
|
||||
<< channelId;
|
||||
continue;
|
||||
}
|
||||
ServerInfo_RoomChannel channel;
|
||||
channel.set_id(channelId.toStdString());
|
||||
channel.set_display_name(settingsCache->value("displayname").toString().toStdString());
|
||||
channel.set_access_level(roomChannelAccessFromKey(settingsCache->value("access").toString()));
|
||||
channel.set_access_level(accessLevel.value());
|
||||
if (settingsCache->contains("writeaccess")) {
|
||||
const QString writeKey = settingsCache->value("writeaccess").toString();
|
||||
if (!writeKey.trimmed().isEmpty()) {
|
||||
auto writeLevel = roomChannelAccessFromKey(writeKey);
|
||||
if (!writeLevel.has_value()) {
|
||||
qWarning() << "Skipping room channel with unknown write access level" << writeKey
|
||||
<< "for id" << channelId;
|
||||
continue;
|
||||
}
|
||||
channel.set_write_access_level(writeLevel.value());
|
||||
}
|
||||
}
|
||||
channel.set_banner_message(settingsCache->value("banner").toString().toStdString());
|
||||
channels.append(channel);
|
||||
}
|
||||
settingsCache->endArray();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue