From df821ab7536583278bb493e3a4622bcb35c3c1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 24 Jun 2022 11:57:44 +0200 Subject: [PATCH] state-res: Enforce integer PLs for room v10 on custom types According to MSC3667 --- crates/ruma-state-res/src/event_auth.rs | 96 +++----- .../src/event_auth/int_power_levels.rs | 94 -------- crates/ruma-state-res/src/lib.rs | 19 +- crates/ruma-state-res/src/power_levels.rs | 220 ++++++++++++++++++ 4 files changed, 254 insertions(+), 175 deletions(-) delete mode 100644 crates/ruma-state-res/src/event_auth/int_power_levels.rs create mode 100644 crates/ruma-state-res/src/power_levels.rs diff --git a/crates/ruma-state-res/src/event_auth.rs b/crates/ruma-state-res/src/event_auth.rs index e7d93b9df1..c311ed8e1a 100644 --- a/crates/ruma-state-res/src/event_auth.rs +++ b/crates/ruma-state-res/src/event_auth.rs @@ -19,11 +19,14 @@ use serde::{de::IgnoredAny, Deserialize}; use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue}; use tracing::{debug, error, info, warn}; -use crate::{room_version::RoomVersion, Error, Event, PowerLevelsContentFields, Result}; - -mod int_power_levels; - -use int_power_levels::IntRoomPowerLevelsEventContent; +use crate::{ + power_levels::{ + deserialize_power_levels, deserialize_power_levels_content_fields, + deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, + }, + room_version::RoomVersion, + Error, Event, Result, +}; // FIXME: field extracting could be bundled for `content` #[derive(Deserialize)] @@ -37,11 +40,6 @@ struct RoomMemberContentFields { join_authorised_via_users_server: Option>, } -#[derive(Deserialize)] -struct PowerLevelsContentInvite { - invite: Int, -} - /// For the given event `kind` what are the relevant auth events that are needed to authenticate /// this `content`. /// @@ -337,14 +335,11 @@ pub fn auth_check( // If type is m.room.third_party_invite let sender_power_level = if let Some(pl) = &power_levels_event { - if let Ok(content) = from_json_str::(pl.content().get()) { - if let Some(level) = content.users.get(sender) { - *level - } else { - content.users_default - } + let content = deserialize_power_levels_content_fields(pl.content().get(), room_version)?; + if let Some(level) = content.users.get(sender) { + *level } else { - int!(0) // TODO if this fails DB error? + content.users_default } } else { // If no power level event found the creator gets 100 everyone else gets 0 @@ -359,7 +354,8 @@ pub fn auth_check( if *incoming_event.event_type() == RoomEventType::RoomThirdPartyInvite { let invite_level = match &power_levels_event { Some(power_levels) => { - from_json_str::(power_levels.content().get())?.invite + deserialize_power_levels_content_invite(power_levels.content().get(), room_version)? + .invite } None => int!(0), }; @@ -408,15 +404,12 @@ pub fn auth_check( if room_version.extra_redaction_checks && *incoming_event.event_type() == RoomEventType::RoomRedaction { - #[derive(Deserialize)] - struct PowerLevelsContentRedact { - redact: Int, - } - - let redact_level = power_levels_event - .and_then(|pl| from_json_str::(pl.content().get()).ok()) - .map(|c| c.redact) - .unwrap_or_else(|| int!(50)); + let redact_level = match power_levels_event { + Some(pl) => { + deserialize_power_levels_content_redact(pl.content().get(), room_version)?.redact + } + None => int!(50), + }; if !check_redaction(room_version, incoming_event, sender_power_level, redact_level)? { return Ok(false); @@ -500,22 +493,18 @@ fn valid_membership_change( // Is the authorised user allowed to invite users into this room let (auth_user_pl, invite_level) = if let Some(pl) = &power_levels_event { // TODO Refactor all powerlevel parsing - let invite = match from_json_str::(pl.content().get()) { - Ok(power_levels) => power_levels.invite, - _ => int!(0), - }; + let invite = + deserialize_power_levels_content_invite(pl.content().get(), room_version)?.invite; - if let Ok(content) = from_json_str::(pl.content().get()) { - let user_pl = if let Some(level) = content.users.get(user_for_join_auth) { - *level - } else { - content.users_default - }; - - (user_pl, invite) + let content = + deserialize_power_levels_content_fields(pl.content().get(), room_version)?; + let user_pl = if let Some(level) = content.users.get(user_for_join_auth) { + *level } else { - (int!(0), invite) - } + content.users_default + }; + + (user_pl, invite) } else { (int!(0), int!(0)) }; @@ -877,31 +866,6 @@ fn check_power_levels( Some(true) } -fn deserialize_power_levels( - content: &str, - room_version: &RoomVersion, -) -> Option { - if room_version.integer_power_levels { - match from_json_str::(content) { - Ok(content) => Some(content.into()), - Err(_) => { - error!("m.room.power_levels event is not valid with integer values"); - None - } - } - } else { - match from_json_str(content) { - Ok(content) => Some(content), - Err(_) => { - error!( - "m.room.power_levels event is not valid with integer or string integer values" - ); - None - } - } - } -} - fn get_deserialize_levels( old: &serde_json::Value, new: &serde_json::Value, diff --git a/crates/ruma-state-res/src/event_auth/int_power_levels.rs b/crates/ruma-state-res/src/event_auth/int_power_levels.rs deleted file mode 100644 index da15d415c4..0000000000 --- a/crates/ruma-state-res/src/event_auth/int_power_levels.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::collections::BTreeMap; - -use js_int::Int; -use ruma_common::{ - events::{room::power_levels::RoomPowerLevelsEventContent, RoomEventType}, - power_levels::{default_power_level, NotificationPowerLevels}, - OwnedUserId, -}; -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct IntRoomPowerLevelsEventContent { - #[serde(default = "default_power_level")] - pub ban: Int, - - #[serde(default)] - pub events: BTreeMap, - - #[serde(default)] - pub events_default: Int, - - #[serde(default)] - pub invite: Int, - - #[serde(default = "default_power_level")] - pub kick: Int, - - #[serde(default = "default_power_level")] - pub redact: Int, - - #[serde(default = "default_power_level")] - pub state_default: Int, - - #[serde(default)] - pub users: BTreeMap, - - #[serde(default)] - pub users_default: Int, - - #[serde(default)] - pub notifications: IntNotificationPowerLevels, -} - -impl From for RoomPowerLevelsEventContent { - fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self { - let IntRoomPowerLevelsEventContent { - ban, - events, - events_default, - invite, - kick, - redact, - state_default, - users, - users_default, - notifications, - } = int_pl; - - let mut pl = Self::new(); - pl.ban = ban; - pl.events = events; - pl.events_default = events_default; - pl.invite = invite; - pl.kick = kick; - pl.redact = redact; - pl.state_default = state_default; - pl.users = users; - pl.users_default = users_default; - pl.notifications = notifications.into(); - - pl - } -} - -#[derive(Deserialize)] -pub struct IntNotificationPowerLevels { - #[serde(default = "default_power_level")] - pub room: Int, -} - -impl Default for IntNotificationPowerLevels { - fn default() -> Self { - Self { room: default_power_level() } - } -} - -impl From for NotificationPowerLevels { - fn from(int_notif: IntNotificationPowerLevels) -> Self { - let mut notif = Self::new(); - notif.room = int_notif.room; - - notif - } -} diff --git a/crates/ruma-state-res/src/lib.rs b/crates/ruma-state-res/src/lib.rs index 9d6b4203c1..1104b54939 100644 --- a/crates/ruma-state-res/src/lib.rs +++ b/crates/ruma-state-res/src/lib.rs @@ -1,7 +1,7 @@ use std::{ borrow::Borrow, cmp::Reverse, - collections::{BTreeMap, BinaryHeap, HashMap, HashSet}, + collections::{BinaryHeap, HashMap, HashSet}, hash::Hash, }; @@ -12,14 +12,14 @@ use ruma_common::{ room::member::{MembershipState, RoomMemberEventContent}, RoomEventType, StateEventType, }, - EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, RoomVersionId, + EventId, MilliSecondsSinceUnixEpoch, RoomVersionId, }; -use serde::Deserialize; use serde_json::from_str as from_json_str; use tracing::{debug, info, trace, warn}; mod error; pub mod event_auth; +mod power_levels; pub mod room_version; mod state_event; #[cfg(test)] @@ -27,6 +27,7 @@ mod test_utils; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; +use power_levels::PowerLevelsContentFields; pub use room_version::RoomVersion; pub use state_event::Event; @@ -335,18 +336,6 @@ where Ok(sorted) } -#[derive(Deserialize)] -struct PowerLevelsContentFields { - #[serde( - default, - deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values" - )] - users: BTreeMap, - - #[serde(default, deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel")] - users_default: Int, -} - /// Find the power level for the sender of `event_id` or return a default value of zero. /// /// Do NOT use this any where but topological sort, we find the power level for the eventId diff --git a/crates/ruma-state-res/src/power_levels.rs b/crates/ruma-state-res/src/power_levels.rs new file mode 100644 index 0000000000..b858504f99 --- /dev/null +++ b/crates/ruma-state-res/src/power_levels.rs @@ -0,0 +1,220 @@ +use std::collections::BTreeMap; + +use js_int::Int; +use ruma_common::{ + events::{room::power_levels::RoomPowerLevelsEventContent, RoomEventType}, + power_levels::{default_power_level, NotificationPowerLevels}, + serde::{btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel}, + OwnedUserId, +}; +use serde::Deserialize; +use serde_json::{from_str as from_json_str, Error}; +use tracing::error; + +use crate::RoomVersion; + +#[derive(Deserialize)] +struct IntRoomPowerLevelsEventContent { + #[serde(default = "default_power_level")] + pub ban: Int, + + #[serde(default)] + pub events: BTreeMap, + + #[serde(default)] + pub events_default: Int, + + #[serde(default)] + pub invite: Int, + + #[serde(default = "default_power_level")] + pub kick: Int, + + #[serde(default = "default_power_level")] + pub redact: Int, + + #[serde(default = "default_power_level")] + pub state_default: Int, + + #[serde(default)] + pub users: BTreeMap, + + #[serde(default)] + pub users_default: Int, + + #[serde(default)] + pub notifications: IntNotificationPowerLevels, +} + +impl From for RoomPowerLevelsEventContent { + fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self { + let IntRoomPowerLevelsEventContent { + ban, + events, + events_default, + invite, + kick, + redact, + state_default, + users, + users_default, + notifications, + } = int_pl; + + let mut pl = Self::new(); + pl.ban = ban; + pl.events = events; + pl.events_default = events_default; + pl.invite = invite; + pl.kick = kick; + pl.redact = redact; + pl.state_default = state_default; + pl.users = users; + pl.users_default = users_default; + pl.notifications = notifications.into(); + + pl + } +} + +#[derive(Deserialize)] +struct IntNotificationPowerLevels { + #[serde(default = "default_power_level")] + pub room: Int, +} + +impl Default for IntNotificationPowerLevels { + fn default() -> Self { + Self { room: default_power_level() } + } +} + +impl From for NotificationPowerLevels { + fn from(int_notif: IntNotificationPowerLevels) -> Self { + let mut notif = Self::new(); + notif.room = int_notif.room; + + notif + } +} + +pub(crate) fn deserialize_power_levels( + content: &str, + room_version: &RoomVersion, +) -> Option { + if room_version.integer_power_levels { + match from_json_str::(content) { + Ok(content) => Some(content.into()), + Err(_) => { + error!("m.room.power_levels event is not valid with integer values"); + None + } + } + } else { + match from_json_str(content) { + Ok(content) => Some(content), + Err(_) => { + error!( + "m.room.power_levels event is not valid with integer or string integer values" + ); + None + } + } + } +} + +#[derive(Deserialize)] +pub(crate) struct PowerLevelsContentFields { + #[serde(default, deserialize_with = "btreemap_deserialize_v1_powerlevel_values")] + pub(crate) users: BTreeMap, + + #[serde(default, deserialize_with = "deserialize_v1_powerlevel")] + pub(crate) users_default: Int, +} + +#[derive(Deserialize)] +struct IntPowerLevelsContentFields { + #[serde(default)] + users: BTreeMap, + + #[serde(default)] + users_default: Int, +} + +impl From for PowerLevelsContentFields { + fn from(pl: IntPowerLevelsContentFields) -> Self { + let IntPowerLevelsContentFields { users, users_default } = pl; + Self { users, users_default } + } +} + +pub(crate) fn deserialize_power_levels_content_fields( + content: &str, + room_version: &RoomVersion, +) -> Result { + if room_version.integer_power_levels { + from_json_str::(content).map(|r| r.into()) + } else { + from_json_str(content) + } +} + +#[derive(Deserialize)] +pub(crate) struct PowerLevelsContentInvite { + #[serde(default, deserialize_with = "deserialize_v1_powerlevel")] + pub(crate) invite: Int, +} + +#[derive(Deserialize)] +struct IntPowerLevelsContentInvite { + #[serde(default)] + invite: Int, +} + +impl From for PowerLevelsContentInvite { + fn from(pl: IntPowerLevelsContentInvite) -> Self { + let IntPowerLevelsContentInvite { invite } = pl; + Self { invite } + } +} + +pub(crate) fn deserialize_power_levels_content_invite( + content: &str, + room_version: &RoomVersion, +) -> Result { + if room_version.integer_power_levels { + from_json_str::(content).map(|r| r.into()) + } else { + from_json_str(content) + } +} + +#[derive(Deserialize)] +pub(crate) struct PowerLevelsContentRedact { + #[serde(default = "default_power_level", deserialize_with = "deserialize_v1_powerlevel")] + pub(crate) redact: Int, +} + +#[derive(Deserialize)] +pub(crate) struct IntPowerLevelsContentRedact { + #[serde(default = "default_power_level")] + redact: Int, +} + +impl From for PowerLevelsContentRedact { + fn from(pl: IntPowerLevelsContentRedact) -> Self { + let IntPowerLevelsContentRedact { redact } = pl; + Self { redact } + } +} + +pub(crate) fn deserialize_power_levels_content_redact( + content: &str, + room_version: &RoomVersion, +) -> Result { + if room_version.integer_power_levels { + from_json_str::(content).map(|r| r.into()) + } else { + from_json_str(content) + } +}