1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-08-11 17:50:59 +00:00

feat: MSC4289, Explicitly privilege room creators (1/2)

This commit is contained in:
Matthias Ahouansou 2025-07-03 12:41:16 +01:00
parent be867db3d9
commit b5e318561c
No known key found for this signature in database
7 changed files with 282 additions and 227 deletions

22
Cargo.lock generated
View file

@ -2521,7 +2521,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.12.5"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"assign",
"js_int",
@ -2540,7 +2540,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.12.2"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"js_int",
"ruma-common",
@ -2552,7 +2552,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.20.4"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"as_variant",
"assign",
@ -2575,7 +2575,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.15.4"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"as_variant",
"base64 0.22.1",
@ -2607,7 +2607,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.30.4"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"as_variant",
"indexmap 2.9.0",
@ -2631,7 +2631,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.11.2"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"bytes",
"headers",
@ -2653,7 +2653,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.10.1"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"js_int",
"thiserror 2.0.12",
@ -2662,7 +2662,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.15.2"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"cfg-if",
"proc-macro-crate",
@ -2677,7 +2677,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.11.0"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"js_int",
"ruma-common",
@ -2689,7 +2689,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.17.1"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@ -2705,7 +2705,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.13.0"
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
dependencies = [
"js_int",
"ruma-common",

View file

@ -23,11 +23,16 @@ use ruma::{
},
int,
serde::JsonObject,
CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId,
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomAliasId, OwnedUserId, RoomAliasId, RoomId,
};
use serde::Deserialize;
use serde_json::{json, value::to_raw_value};
use std::{cmp::max, collections::BTreeMap, sync::Arc};
use tracing::{info, warn};
use std::{
cmp::max,
collections::{BTreeMap, HashSet},
sync::Arc,
};
use tracing::{error, info, warn};
/// # `POST /_matrix/client/r0/createRoom`
///
@ -142,9 +147,33 @@ pub async fn create_room_route(
.expect("Supported room version must have rules.")
.authorization;
let mut users = BTreeMap::new();
if !rules.explicitly_privilege_room_creators {
users.insert(sender_user.clone(), int!(100));
}
// Figure out preset. We need it for preset specific events
let preset = body.preset.clone().unwrap_or(match &body.visibility {
room::Visibility::Private => RoomPreset::PrivateChat,
room::Visibility::Public => RoomPreset::PublicChat,
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut additional_creators: HashSet<OwnedUserId, _> = HashSet::new();
if preset == RoomPreset::TrustedPrivateChat {
if rules.additional_room_creators {
additional_creators.extend(body.invite.clone())
} else {
for invited_user in &body.invite {
users.insert(invited_user.clone(), int!(100));
}
}
}
let content = match &body.creation_content {
Some(content) => {
let mut content = content
Some(raw_content) => {
let mut content = raw_content
.deserialize_as_unchecked::<CanonicalJsonObject>()
.expect("Invalid creation content");
@ -157,6 +186,27 @@ pub async fn create_room_route(
);
}
if rules.additional_room_creators && !additional_creators.is_empty() {
#[derive(Deserialize)]
struct AdditionalCreators {
additional_creators: Vec<OwnedUserId>,
}
if let Ok(AdditionalCreators {
additional_creators: ac,
}) = raw_content.deserialize_as_unchecked()
{
additional_creators.extend(ac);
}
content.insert(
"additional_creators".into(),
json!(&additional_creators).try_into().map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid additional creators")
})?,
);
}
content.insert(
"room_version".into(),
json!(room_version.as_str()).try_into().map_err(|_| {
@ -166,24 +216,22 @@ pub async fn create_room_route(
content
}
None => {
let content = if rules.use_room_create_sender {
RoomCreateEventContent::new_v11()
} else {
RoomCreateEventContent::new_v1(sender_user.clone())
let content = RoomCreateEventContent {
additional_creators: additional_creators.into_iter().collect(),
room_version,
..if rules.use_room_create_sender {
RoomCreateEventContent::new_v11()
} else {
RoomCreateEventContent::new_v1(sender_user.clone())
}
};
let mut content = serde_json::from_str::<CanonicalJsonObject>(
serde_json::from_str::<CanonicalJsonObject>(
to_raw_value(&content)
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
.get(),
)
.unwrap();
content.insert(
"room_version".into(),
json!(room_version.as_str()).try_into().map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
})?,
);
content
.expect("room create event content created by us is valid")
}
};
@ -250,26 +298,9 @@ pub async fn create_room_route(
.await?;
// 3. Power levels
// Figure out preset. We need it for preset specific events
let preset = body.preset.clone().unwrap_or(match &body.visibility {
room::Visibility::Private => RoomPreset::PrivateChat,
room::Visibility::Public => RoomPreset::PublicChat,
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut users = BTreeMap::new();
users.insert(sender_user.clone(), int!(100));
if preset == RoomPreset::TrustedPrivateChat {
for invite_ in &body.invite {
users.insert(invite_.clone(), int!(100));
}
}
let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
users,
..Default::default()
..RoomPowerLevelsEventContent::new(&rules)
})
.expect("event is valid, we just created it");
@ -587,7 +618,8 @@ pub async fn upgrade_room_route(
let rules = body
.new_version
.rules()
.expect("Supported room version must have rules.");
.expect("Supported room version must have rules.")
.authorization;
// Create a replacement room
let replacement_room = RoomId::new(services().globals.server_name());
@ -663,7 +695,7 @@ pub async fn upgrade_room_route(
));
// Send a m.room.create event containing a predecessor field and the applicable room_version
if rules.authorization.use_room_create_sender {
if rules.use_room_create_sender {
create_event_content.remove("creator");
} else {
create_event_content.insert(
@ -674,6 +706,18 @@ pub async fn upgrade_room_route(
);
}
if rules.additional_room_creators && !body.additional_creators.is_empty() {
create_event_content.insert(
"additional_creators".into(),
json!(&body.additional_creators).try_into().map_err(|_| {
Error::BadRequest(
ErrorKind::BadJson,
"Failed to convert provided additional additional creators to JSON",
)
})?,
);
}
create_event_content.insert(
"room_version".into(),
json!(&body.new_version)
@ -764,7 +808,7 @@ pub async fn upgrade_room_route(
// Replicate transferable state events to the new room
for event_type in transferable_state_events {
let event_content =
let mut event_content =
match services()
.rooms
.state_accessor
@ -774,6 +818,31 @@ pub async fn upgrade_room_route(
None => continue, // Skipping missing events.
};
if event_type == StateEventType::RoomPowerLevels && rules.explicitly_privilege_room_creators
{
let mut pl_event_content: CanonicalJsonObject =
serde_json::from_str(event_content.get()).map_err(|e| {
error!(
"Invalid m.room.power_levels event content in room {}: {e}",
body.room_id
);
Error::BadDatabase("Invalid m.room.power_levels event content in room")
})?;
if let Some(CanonicalJsonValue::Object(users)) = pl_event_content.get_mut("users") {
users.remove(sender_user.as_str());
if rules.additional_room_creators {
for user in &body.additional_creators {
users.remove(user.as_str());
}
}
}
event_content = to_raw_value(&pl_event_content)
.expect("Must serialize, only changes made was removing keys")
}
services()
.rooms
.timeline

View file

@ -1756,7 +1756,9 @@ impl Service {
// 3. Power levels
let mut users = BTreeMap::new();
users.insert(conduit_user.to_owned(), 100.into());
if !rules.explicitly_privilege_room_creators {
users.insert(conduit_user.to_owned(), 100.into());
}
services()
.rooms
@ -1766,7 +1768,7 @@ impl Service {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
..RoomPowerLevelsEventContent::new(&rules)
})
.expect("event is valid, we just created it"),
unsigned: None,
@ -2010,8 +2012,16 @@ impl Service {
.await?;
// Set power level
let room_version = services().rooms.state.get_room_version(&room_id)?;
let rules = room_version
.rules()
.expect("Supported room version must have rules.")
.authorization;
let mut users = BTreeMap::new();
users.insert(conduit_user.to_owned(), 100.into());
if !rules.explicitly_privilege_room_creators {
users.insert(conduit_user.to_owned(), 100.into());
}
users.insert(user_id.to_owned(), 100.into());
services()
@ -2022,7 +2032,7 @@ impl Service {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
..RoomPowerLevelsEventContent::new(&rules)
})
.expect("event is valid, we just created it"),
unsigned: None,

View file

@ -13,7 +13,7 @@ use ruma::{
},
IncomingResponse, OutgoingRequest, SendAccessToken,
},
events::{room::power_levels::RoomPowerLevelsEventContent, StateEventType, TimelineEventType},
events::TimelineEventType,
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
serde::Raw,
uint, RoomId, UInt, UserId,
@ -139,21 +139,12 @@ impl Service {
let mut notify = None;
let mut tweaks = Vec::new();
let power_levels: RoomPowerLevelsEventContent = services()
.rooms
.state_accessor
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_str(ev.content.get())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
})
.transpose()?
.unwrap_or_default();
let power_levels = services().rooms.state_accessor.power_levels(&pdu.room_id)?;
for action in self.get_actions(
user,
&ruleset,
&power_levels,
power_levels.into(),
&pdu.to_sync_room_event(),
&pdu.room_id,
)? {
@ -188,16 +179,10 @@ impl Service {
&self,
user: &UserId,
ruleset: &'a Ruleset,
power_levels: &RoomPowerLevelsEventContent,
power_levels: PushConditionPowerLevelsCtx,
pdu: &Raw<AnySyncTimelineEvent>,
room_id: &RoomId,
) -> Result<&'a [Action]> {
let power_levels = PushConditionPowerLevelsCtx {
users: power_levels.users.clone(),
users_default: power_levels.users_default,
notifications: power_levels.notifications.clone(),
};
let ctx = PushConditionRoomCtx {
room_id: room_id.to_owned(),
member_count: 10_u32.into(), // TODO: get member count efficiently

View file

@ -2,7 +2,6 @@ mod data;
pub use data::Data;
use rand::seq::SliceRandom;
use tracing::error;
use crate::{services, Error, Result};
use ruma::{
@ -11,10 +10,7 @@ use ruma::{
client::{alias::get_alias, error::ErrorKind},
federation,
},
events::{
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
StateEventType,
},
events::StateEventType,
OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, UserId,
};
@ -55,27 +51,14 @@ impl Service {
{
Ok(true)
// Checking whether the user is able to change canonical aliases of the room
} else if let Some(event) = services().rooms.state_accessor.room_state_get(
&room_id,
&StateEventType::RoomPowerLevels,
"",
)? {
serde_json::from_str(event.content.get())
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
})
// If there is no power levels event, only the room creator can change canonical aliases
} else if let Some(event) = services().rooms.state_accessor.room_state_get(
&room_id,
&StateEventType::RoomCreate,
"",
)? {
Ok(event.sender == user_id)
} else {
error!("Room {} has no m.room.create event (VERY BAD)!", room_id);
Err(Error::bad_database("Room has no m.room.create event"))
services()
.rooms
.state_accessor
.power_levels(&room_id)
.map(|power_levels| {
power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
})
}
}

View file

@ -20,7 +20,7 @@ use ruma::{
StateEventType,
},
room::{JoinRuleSummary, RoomMembership},
state_res::Event,
state_res::{events::RoomCreateEvent, Event},
EventId, JsOption, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
@ -361,43 +361,19 @@ impl Service {
room_id: &RoomId,
federation: bool,
) -> Result<bool> {
self.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.map(|e| {
serde_json::from_str(e.content.get())
.map(|c: RoomPowerLevelsEventContent| c.into())
.map(|e: RoomPowerLevels| {
e.user_can_redact_event_of_other(sender)
|| e.user_can_redact_own_event(sender)
&& if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts)
{
if federation {
pdu.sender().server_name() == sender.server_name()
} else {
pdu.sender == sender
}
} else {
false
}
})
.map_err(|_| {
Error::bad_database("Invalid m.room.power_levels event in database")
})
})
// Falling back on m.room.create to judge power levels
.unwrap_or_else(|| {
if let Some(pdu) = self.room_state_get(room_id, &StateEventType::RoomCreate, "")? {
Ok(pdu.sender == sender
|| if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) {
pdu.sender == sender
self.power_levels(room_id).map(|power_levels| {
power_levels.user_can_redact_event_of_other(sender)
|| power_levels.user_can_redact_own_event(sender)
&& if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) {
if federation {
pdu.sender().server_name() == sender.server_name()
} else {
false
})
} else {
Err(Error::bad_database(
"No m.room.power_levels or m.room.create events in database for room",
))
}
})
pdu.sender == sender
}
} else {
false
}
})
}
/// Checks if guests are able to join a given room
@ -485,4 +461,42 @@ impl Service {
}
room_ids
}
/// Gets the effective power levels of a room, regardless of if there is an
/// `m.rooms.power_levels` state.
pub fn power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels> {
let power_levels: Option<RoomPowerLevelsEventContent> = self
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_str(ev.content.get())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event."))
})
.transpose()?;
let room_create = self
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
.map(RoomCreateEvent::new)
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?;
let room_version = room_create.room_version().map_err(|e| {
error!("Invalid room version in room create content for room {room_id}: {e}");
Error::BadDatabase("Room Create content had invalid room version")
})?;
let rules = room_version
.rules()
.expect("Supported room version must have rules.")
.authorization;
room_create
// NOTE: This is because project hydra's client-side was made public before the server
// side. This will be fixed by using `creators` instead in part 2/2.
.creator(&rules)
.map_err(|e| {
error!("Failed to get creators of room id {}: {e}", room_id);
Error::BadDatabase("RoomCreateEvent has invalid creators")
})
.map(|creator| {
RoomPowerLevels::new(power_levels.into(), &rules, [creator.into_owned()])
})
}
}

View file

@ -15,12 +15,11 @@ use ruma::{
push_rules::PushRulesEvent,
room::{
canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent,
encrypted::Relation, member::MembershipState,
power_levels::RoomPowerLevelsEventContent, redaction::RoomRedactionEventContent,
encrypted::Relation, member::MembershipState, redaction::RoomRedactionEventContent,
},
GlobalAccountDataEventType, StateEventType, TimelineEventType,
},
push::{Action, Ruleset, Tweak},
push::{Action, PushConditionPowerLevelsCtx, Ruleset, Tweak},
state_res::{self, Event},
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId,
@ -290,96 +289,99 @@ impl Service {
drop(insert_lock);
// See if the event matches any known pushers
let power_levels: RoomPowerLevelsEventContent = services()
.rooms
.state_accessor
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_str(ev.content.get())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
})
.transpose()?
.unwrap_or_default();
//
// Will fail if this is the first event (the create event), which is fine, since it cannot
// notify anyone else anyways
if pdu.kind != TimelineEventType::RoomCreate
|| pdu.state_key.as_deref().is_none_or(|key| !key.is_empty())
{
if let Ok(power_levels) = services()
.rooms
.state_accessor
.power_levels(&pdu.room_id)
.map(PushConditionPowerLevelsCtx::from)
{
let sync_pdu = pdu.to_sync_room_event();
let sync_pdu = pdu.to_sync_room_event();
let mut notifies = Vec::new();
let mut highlights = Vec::new();
let mut notifies = Vec::new();
let mut highlights = Vec::new();
let mut push_target = services()
.rooms
.state_cache
.get_our_real_users(&pdu.room_id)?;
let mut push_target = services()
.rooms
.state_cache
.get_our_real_users(&pdu.room_id)?;
if pdu.kind == TimelineEventType::RoomMember {
if let Some(state_key) = &pdu.state_key {
let target_user_id = UserId::parse(state_key.clone())
.expect("This state_key was previously validated");
if pdu.kind == TimelineEventType::RoomMember {
if let Some(state_key) = &pdu.state_key {
let target_user_id = UserId::parse(state_key.clone())
.expect("This state_key was previously validated");
if !push_target.contains(&target_user_id) {
let mut target = push_target.as_ref().clone();
target.insert(target_user_id);
push_target = Arc::new(target);
}
}
}
for user in push_target.iter() {
// Don't notify the user of their own events
if user == &pdu.sender {
continue;
}
let rules_for_user = services()
.account_data
.get(
None,
user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.map(|event| {
serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid push rules event in db."))
})
.transpose()?
.map(|ev: PushRulesEvent| ev.content.global)
.unwrap_or_else(|| Ruleset::server_default(user));
let mut highlight = false;
let mut notify = false;
for action in services().pusher.get_actions(
user,
&rules_for_user,
&power_levels,
&sync_pdu,
&pdu.room_id,
)? {
match action {
Action::Notify => notify = true,
Action::SetTweak(Tweak::Highlight(true)) => {
highlight = true;
if !push_target.contains(&target_user_id) {
let mut target = push_target.as_ref().clone();
target.insert(target_user_id);
push_target = Arc::new(target);
}
}
_ => {}
};
}
}
if notify {
notifies.push(user.clone());
}
for user in push_target.iter() {
// Don't notify the user of their own events
if user == &pdu.sender {
continue;
}
if highlight {
highlights.push(user.clone());
}
let rules_for_user = services()
.account_data
.get(
None,
user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.map(|event| {
serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid push rules event in db."))
})
.transpose()?
.map(|ev: PushRulesEvent| ev.content.global)
.unwrap_or_else(|| Ruleset::server_default(user));
for push_key in services().pusher.get_pushkeys(user) {
services().sending.send_push_pdu(&pdu_id, user, push_key?)?;
let mut highlight = false;
let mut notify = false;
for action in services().pusher.get_actions(
user,
&rules_for_user,
power_levels.clone(),
&sync_pdu,
&pdu.room_id,
)? {
match action {
Action::Notify => notify = true,
Action::SetTweak(Tweak::Highlight(true)) => {
highlight = true;
}
_ => {}
};
}
if notify {
notifies.push(user.clone());
}
if highlight {
highlights.push(user.clone());
}
for push_key in services().pusher.get_pushkeys(user) {
services().sending.send_push_pdu(&pdu_id, user, push_key?)?;
}
}
self.db
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
}
}
self.db
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
match pdu.kind {
TimelineEventType::RoomRedaction => {
let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?;
@ -1167,16 +1169,8 @@ impl Service {
return Ok(());
}
let power_levels: RoomPowerLevelsEventContent = services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_str(ev.content.get())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
})
.transpose()?
.unwrap_or_default();
let power_levels = services().rooms.state_accessor.power_levels(room_id)?;
let mut admin_servers = power_levels
.users
.iter()