[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:
BruebachL 2026-10-16 22:46:38 +02:00 • committed by GitHub
parent 17d2e8d86e
commit 05dcde69db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 211 additions and 29 deletions

View file

@ -336,17 +336,35 @@ roomlist\1\game_types\3\name="GameType3"
; channel every room has. Each channel needs a stable ASCII id, a display name
; (server data, like the room name) and an access level. Supported levels, from
; least to most restrictive, are: public, registered, judge, moderator, admin
; and developer. A channel is readable and writable by every member whose own
; privilege level is at least the channel's level -- so a moderator channel is
; also reachable by admins and developers, and an admin channel by developers.
; and developer. Access is evaluated against the member's user flags, not as a
; ladder: a moderator channel admits moderators (admins hold the moderator
; flag alongside the admin flag, so they are admitted too), an admin channel
; admits only admins, and a developer channel admits only developers and
; admins. In particular, developers cannot read moderator or admin channels.
; A judge channel additionally admits moderators.
; Public channels are visible to every room member.
roomlist\1\channels\size=2
;
; Write access defaults to the read level, so by default a channel is readable
; and writable by the same members. Set writeaccess higher for read-mostly
; channels (e.g. a developer news feed readable by everyone but writable only
; by developers). Members who can read but not write see the channel with a
; disabled message input.
;
; banner is an optional static notice shown at the top of the channel, similar
; to the room joinmessage on Main. Empty or unset = none.
roomlist\1\channels\size=3
roomlist\1\channels\1\id=looking-for-players
roomlist\1\channels\1\displayname="Looking for players"
roomlist\1\channels\1\access=public
roomlist\1\channels\1\banner="Find opponents here. Be friendly!"
roomlist\1\channels\2\id=moderators
roomlist\1\channels\2\displayname="Moderators"
roomlist\1\channels\2\access=moderator
roomlist\1\channels\3\id=developer-news
roomlist\1\channels\3\displayname="Developer news"
roomlist\1\channels\3\access=public
roomlist\1\channels\3\writeaccess=developer
roomlist\1\channels\3\banner="Official announcements from the developers."
[game]