From 053bdf00dae5f4b8a621405e00d0b8f6a33d81c1 Mon Sep 17 00:00:00 2001 From: ember33 Date: Tue, 17 Mar 2026 16:54:19 +0100 Subject: [PATCH] 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) --- src/service/rooms/mod.rs | 2 + src/service/rooms/roles/mod.rs | 88 ++++++++++++++++++++++++++++++++++ src/service/services.rs | 1 + 3 files changed, 91 insertions(+) create mode 100644 src/service/rooms/roles/mod.rs diff --git a/src/service/rooms/mod.rs b/src/service/rooms/mod.rs index 44a83582..bf4304f7 100644 --- a/src/service/rooms/mod.rs +++ b/src/service/rooms/mod.rs @@ -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, pub pdu_metadata: Arc, pub read_receipt: Arc, + pub roles: Arc, pub search: Arc, pub short: Arc, pub spaces: Arc, diff --git a/src/service/rooms/roles/mod.rs b/src/service/rooms/roles/mod.rs new file mode 100644 index 00000000..47229a3f --- /dev/null +++ b/src/service/rooms/roles/mod.rs @@ -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, + /// 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>>, + /// Space ID -> user ID -> assigned role names + pub user_roles: RwLock>>>, + /// Space ID -> child room ID -> required role names + pub room_requirements: RwLock>>>, + /// Child room ID -> parent space ID + pub room_to_space: RwLock>, +} + +#[allow(dead_code)] +struct Services { + state_accessor: Dep, + state_cache: Dep, + state: Dep, + spaces: Dep, + timeline: Dep, +} + +#[async_trait] +impl crate::Service for Service { + fn build(args: crate::Args<'_>) -> Result> { + Ok(Arc::new(Self { + services: Services { + state_accessor: args + .depend::("rooms::state_accessor"), + state_cache: args.depend::("rooms::state_cache"), + state: args.depend::("rooms::state"), + spaces: args.depend::("rooms::spaces"), + timeline: args.depend::("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!()) } +} diff --git a/src/service/services.rs b/src/service/services.rs index 60a7eeab..6356c6ea 100644 --- a/src/service/services.rs +++ b/src/service/services.rs @@ -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),