fix(spaces): improve feature flag isolation for disabled state
- Gate memory_usage() and clear_cache() with is_enabled() - Gate populate_space() and get_parent_space() as defense-in-depth - All admin commands now refuse when feature is disabled with a clear message pointing to the config option - Prefix memory labels with space_ for disambiguation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
40646eb4ba
commit
6fa67ed489
2 changed files with 42 additions and 4 deletions
|
|
@ -11,6 +11,19 @@ use conduwuit::matrix::pdu::PduBuilder;
|
|||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
macro_rules! require_enabled {
|
||||
($self:expr) => {
|
||||
if !$self.services.rooms.roles.is_enabled() {
|
||||
return $self
|
||||
.write_str(
|
||||
"Space permission cascading is disabled. \
|
||||
Enable it with `space_permission_cascading = true` in your config.",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! custom_state_pdu {
|
||||
($event_type:expr, $state_key:expr, $content:expr) => {
|
||||
PduBuilder {
|
||||
|
|
@ -82,6 +95,7 @@ pub enum SpaceRolesCommand {
|
|||
|
||||
#[admin_command]
|
||||
async fn list(&self, space: OwnedRoomOrAliasId) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -124,6 +138,7 @@ async fn add(
|
|||
description: Option<String>,
|
||||
power_level: Option<i64>,
|
||||
) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -170,6 +185,7 @@ async fn add(
|
|||
|
||||
#[admin_command]
|
||||
async fn remove(&self, space: OwnedRoomOrAliasId, role_name: String) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -216,6 +232,7 @@ async fn assign(
|
|||
user_id: OwnedUserId,
|
||||
role_name: String,
|
||||
) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -266,6 +283,7 @@ async fn revoke(
|
|||
user_id: OwnedUserId,
|
||||
role_name: String,
|
||||
) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -317,6 +335,7 @@ async fn require(
|
|||
room_id: OwnedRoomId,
|
||||
role_name: String,
|
||||
) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -367,6 +386,7 @@ async fn unrequire(
|
|||
room_id: OwnedRoomId,
|
||||
role_name: String,
|
||||
) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -413,6 +433,7 @@ async fn unrequire(
|
|||
|
||||
#[admin_command]
|
||||
async fn user(&self, space: OwnedRoomOrAliasId, user_id: OwnedUserId) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
@ -448,6 +469,7 @@ async fn user(&self, space: OwnedRoomOrAliasId, user_id: OwnedUserId) -> Result
|
|||
|
||||
#[admin_command]
|
||||
async fn room(&self, space: OwnedRoomOrAliasId, room_id: OwnedRoomId) -> Result {
|
||||
require_enabled!(self);
|
||||
let space_id = self.services.rooms.alias.resolve(&space).await?;
|
||||
if !matches!(
|
||||
self.services.rooms.state_accessor.get_room_type(&space_id).await,
|
||||
|
|
|
|||
|
|
@ -91,20 +91,28 @@ impl crate::Service for Service {
|
|||
}
|
||||
|
||||
async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
|
||||
if !self.is_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let roles = self.roles.read().await.len();
|
||||
let user_roles = self.user_roles.read().await.len();
|
||||
let room_requirements = self.room_requirements.read().await.len();
|
||||
let room_to_space = self.room_to_space.read().await.len();
|
||||
|
||||
writeln!(out, "roles: {roles}")?;
|
||||
writeln!(out, "user_roles: {user_roles}")?;
|
||||
writeln!(out, "room_requirements: {room_requirements}")?;
|
||||
writeln!(out, "room_to_space: {room_to_space}")?;
|
||||
writeln!(out, "space_roles_definitions: {roles}")?;
|
||||
writeln!(out, "space_user_roles: {user_roles}")?;
|
||||
writeln!(out, "space_room_requirements: {room_requirements}")?;
|
||||
writeln!(out, "space_room_to_space_index: {room_to_space}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_cache(&self) {
|
||||
if !self.is_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.roles.write().await.clear();
|
||||
self.user_roles.write().await.clear();
|
||||
self.room_requirements.write().await.clear();
|
||||
|
|
@ -218,6 +226,10 @@ pub async fn ensure_default_roles(&self, space_id: &RoomId) -> Result {
|
|||
/// `m.space.child` state events and indexes them for fast lookup.
|
||||
#[implement(Service)]
|
||||
pub async fn populate_space(&self, space_id: &RoomId) {
|
||||
if !self.is_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Read m.space.roles (state key: "")
|
||||
let roles_event_type = StateEventType::from("m.space.roles".to_owned());
|
||||
if let Ok(content) = self
|
||||
|
|
@ -403,6 +415,10 @@ pub async fn user_qualifies_for_room(
|
|||
/// Get the parent Space of a child room, if any.
|
||||
#[implement(Service)]
|
||||
pub async fn get_parent_space(&self, room_id: &RoomId) -> Option<OwnedRoomId> {
|
||||
if !self.is_enabled() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.room_to_space.read().await.get(room_id).cloned()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue