feat(spaces): add space roles service for permission cascading

Create rooms::roles::Service with in-memory caches for role definitions,
user-role assignments, room requirements, and room-to-space mappings.
Register the service in the service stack alongside other room services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ember33 2026-03-17 16:54:19 +01:00
parent c5ffc4963c
commit 053bdf00da
3 changed files with 91 additions and 0 deletions

View file

@ -7,6 +7,7 @@ pub mod metadata;
pub mod outlier;
pub mod pdu_metadata;
pub mod read_receipt;
pub mod roles;
pub mod search;
pub mod short;
pub mod spaces;
@ -31,6 +32,7 @@ pub struct Service {
pub outlier: Arc<outlier::Service>,
pub pdu_metadata: Arc<pdu_metadata::Service>,
pub read_receipt: Arc<read_receipt::Service>,
pub roles: Arc<roles::Service>,
pub search: Arc<search::Service>,
pub short: Arc<short::Service>,
pub spaces: Arc<spaces::Service>,

View file

@ -0,0 +1,88 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt::Write,
sync::Arc,
};
use async_trait::async_trait;
use conduwuit::Result;
use ruma::{OwnedRoomId, OwnedUserId};
use tokio::sync::RwLock;
use crate::{Dep, rooms};
/// Definition of a role within a space, including its permissions.
#[derive(Clone, Debug)]
pub struct RoleDefinition {
/// Human-readable name of the role.
pub name: String,
/// Set of permission strings granted by this role.
pub permissions: HashSet<String>,
/// Priority for ordering/conflict resolution (higher = more priority).
pub priority: i64,
}
pub struct Service {
#[allow(dead_code)]
services: Services,
/// Space ID -> role name -> role definition
pub roles: RwLock<HashMap<OwnedRoomId, BTreeMap<String, RoleDefinition>>>,
/// Space ID -> user ID -> assigned role names
pub user_roles: RwLock<HashMap<OwnedRoomId, HashMap<OwnedUserId, HashSet<String>>>>,
/// Space ID -> child room ID -> required role names
pub room_requirements: RwLock<HashMap<OwnedRoomId, HashMap<OwnedRoomId, HashSet<String>>>>,
/// Child room ID -> parent space ID
pub room_to_space: RwLock<HashMap<OwnedRoomId, OwnedRoomId>>,
}
#[allow(dead_code)]
struct Services {
state_accessor: Dep<rooms::state_accessor::Service>,
state_cache: Dep<rooms::state_cache::Service>,
state: Dep<rooms::state::Service>,
spaces: Dep<rooms::spaces::Service>,
timeline: Dep<rooms::timeline::Service>,
}
#[async_trait]
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
services: Services {
state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
state: args.depend::<rooms::state::Service>("rooms::state"),
spaces: args.depend::<rooms::spaces::Service>("rooms::spaces"),
timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"),
},
roles: RwLock::new(HashMap::new()),
user_roles: RwLock::new(HashMap::new()),
room_requirements: RwLock::new(HashMap::new()),
room_to_space: RwLock::new(HashMap::new()),
}))
}
async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
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}")?;
Ok(())
}
async fn clear_cache(&self) {
self.roles.write().await.clear();
self.user_roles.write().await.clear();
self.room_requirements.write().await.clear();
self.room_to_space.write().await.clear();
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}

View file

@ -94,6 +94,7 @@ impl Services {
outlier: build!(rooms::outlier::Service),
pdu_metadata: build!(rooms::pdu_metadata::Service),
read_receipt: build!(rooms::read_receipt::Service),
roles: build!(rooms::roles::Service),
search: build!(rooms::search::Service),
short: build!(rooms::short::Service),
spaces: build!(rooms::spaces::Service),