diff --git a/src/admin/space/roles.rs b/src/admin/space/roles.rs index 7f0206ff..b29ff20c 100644 --- a/src/admin/space/roles.rs +++ b/src/admin/space/roles.rs @@ -1,5 +1,5 @@ use clap::Subcommand; -use conduwuit::{Err, Result}; +use conduwuit::{Err, Event, Result}; use conduwuit_core::matrix::space_roles::{ RoleDefinition, SpaceRoleMemberEventContent, SpaceRoleRoomEventContent, SpaceRolesEventContent, SPACE_ROLES_EVENT_TYPE, SPACE_ROLE_MEMBER_EVENT_TYPE, @@ -224,6 +224,89 @@ async fn remove(&self, space: OwnedRoomOrAliasId, role_name: String) -> Result { ) .await?; + // Cascade: remove the role from all users' member events + let member_event_type = StateEventType::from(SPACE_ROLE_MEMBER_EVENT_TYPE.to_owned()); + if let Ok(shortstatehash) = self + .services + .rooms + .state + .get_room_shortstatehash(&space_id) + .await + { + use futures::StreamExt; + + let user_entries: Vec<(_, ruma::OwnedEventId)> = self + .services + .rooms + .state_accessor + .state_keys_with_ids(shortstatehash, &member_event_type) + .collect() + .await; + + for (state_key, event_id) in user_entries { + if let Ok(pdu) = self.services.rooms.timeline.get_pdu(&event_id).await { + if let Ok(mut member_content) = + pdu.get_content::() + { + if member_content.roles.contains(&role_name) { + member_content.roles.retain(|r| r != &role_name); + self.services + .rooms + .timeline + .build_and_append_pdu( + custom_state_pdu!( + SPACE_ROLE_MEMBER_EVENT_TYPE, + &state_key, + &member_content + ), + server_user, + Some(&space_id), + &state_lock, + ) + .await?; + } + } + } + } + + // Cascade: remove the role from all rooms' role requirement events + let room_event_type = StateEventType::from(SPACE_ROLE_ROOM_EVENT_TYPE.to_owned()); + + let room_entries: Vec<(_, ruma::OwnedEventId)> = self + .services + .rooms + .state_accessor + .state_keys_with_ids(shortstatehash, &room_event_type) + .collect() + .await; + + for (state_key, event_id) in room_entries { + if let Ok(pdu) = self.services.rooms.timeline.get_pdu(&event_id).await { + if let Ok(mut room_content) = + pdu.get_content::() + { + if room_content.required_roles.contains(&role_name) { + room_content.required_roles.retain(|r| r != &role_name); + self.services + .rooms + .timeline + .build_and_append_pdu( + custom_state_pdu!( + SPACE_ROLE_ROOM_EVENT_TYPE, + &state_key, + &room_content + ), + server_user, + Some(&space_id), + &state_lock, + ) + .await?; + } + } + } + } + } + self.write_str(&format!("Removed role '{role_name}' from space {space_id}.")) .await } @@ -236,6 +319,23 @@ async fn assign( role_name: String, ) -> Result { let space_id = resolve_space!(self, space); + + // Read current role definitions to validate the role name + let roles_event_type = StateEventType::from(SPACE_ROLES_EVENT_TYPE.to_owned()); + let role_defs: SpaceRolesEventContent = self + .services + .rooms + .state_accessor + .room_state_get_content(&space_id, &roles_event_type, "") + .await + .unwrap_or_default(); + + if !role_defs.roles.contains_key(&role_name) { + return self + .write_str(&format!("Error: Role '{}' does not exist in this space.", role_name)) + .await; + } + let member_event_type = StateEventType::from(SPACE_ROLE_MEMBER_EVENT_TYPE.to_owned()); let mut content: SpaceRoleMemberEventContent = self @@ -325,6 +425,23 @@ async fn require( role_name: String, ) -> Result { let space_id = resolve_space!(self, space); + + // Read current role definitions to validate the role name + let roles_event_type = StateEventType::from(SPACE_ROLES_EVENT_TYPE.to_owned()); + let role_defs: SpaceRolesEventContent = self + .services + .rooms + .state_accessor + .room_state_get_content(&space_id, &roles_event_type, "") + .await + .unwrap_or_default(); + + if !role_defs.roles.contains_key(&role_name) { + return self + .write_str(&format!("Error: Role '{}' does not exist in this space.", role_name)) + .await; + } + let room_event_type = StateEventType::from(SPACE_ROLE_ROOM_EVENT_TYPE.to_owned()); let mut content: SpaceRoleRoomEventContent = self diff --git a/src/service/rooms/roles/mod.rs b/src/service/rooms/roles/mod.rs index f72558cf..cd78c175 100644 --- a/src/service/rooms/roles/mod.rs +++ b/src/service/rooms/roles/mod.rs @@ -68,8 +68,6 @@ struct Services { state_accessor: Dep, state_cache: Dep, state: Dep, - #[allow(dead_code)] - spaces: Dep, timeline: Dep, } @@ -84,7 +82,6 @@ impl crate::Service for Service { .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"), }, server: args.server.clone(), @@ -698,6 +695,23 @@ impl Service { debug_warn!("Failed to sync PLs in {child_room_id}: {e}"); } } + // Revalidate all space members against all child rooms + let space_members: Vec = this + .services + .state_cache + .room_members(&space_id) + .map(ToOwned::to_owned) + .collect() + .await; + for member in &space_members { + if let Err(e) = + this.kick_unqualified_from_rooms(&space_id, member).await + { + debug_warn!( + "Role definition revalidation kick failed for {member}: {e}" + ); + } + } }, | SPACE_ROLE_MEMBER_EVENT_TYPE => { // User's roles changed — auto-join/kick + PL sync diff --git a/src/service/rooms/timeline/append.rs b/src/service/rooms/timeline/append.rs index 611353ed..eb6657d9 100644 --- a/src/service/rooms/timeline/append.rs +++ b/src/service/rooms/timeline/append.rs @@ -370,13 +370,18 @@ where | SPACE_ROLES_EVENT_TYPE | SPACE_ROLE_MEMBER_EVENT_TYPE | SPACE_ROLE_ROOM_EVENT_TYPE => { - let roles: Arc = - Arc::clone(&*self.services.roles); - roles.handle_state_event_change( - room_id.to_owned(), - event_type_str, - state_key.to_string(), - ); + if matches!( + self.services.state_accessor.get_room_type(room_id).await, + Ok(ruma::room::RoomType::Space) + ) { + let roles: Arc = + Arc::clone(&*self.services.roles); + roles.handle_state_event_change( + room_id.to_owned(), + event_type_str, + state_key.to_string(), + ); + } }, | _ => {}, }