Skip to content

Commit a47961a

Browse files
committed
refactor: Adapt to changes in power levels
Signed-off-by: Kévin Commaille <[email protected]>
1 parent fc595fc commit a47961a

File tree

20 files changed

+273
-100
lines changed

20 files changed

+273
-100
lines changed

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ use ruma::{
7878
RoomAccountDataEvent as RumaRoomAccountDataEvent,
7979
},
8080
push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
81+
room_version_rules::AuthorizationRules,
8182
OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
8283
};
8384
use serde::{Deserialize, Serialize};
@@ -1898,7 +1899,7 @@ pub struct PowerLevels {
18981899

18991900
impl From<PowerLevels> for RoomPowerLevelsEventContent {
19001901
fn from(value: PowerLevels) -> Self {
1901-
let mut power_levels = RoomPowerLevelsEventContent::new();
1902+
let mut power_levels = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
19021903

19031904
if let Some(users_default) = value.users_default {
19041905
power_levels.users_default = users_default.into();

bindings/matrix-sdk-ffi/src/room_member.rs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use matrix_sdk::room::{RoomMember as SdkRoomMember, RoomMemberRole};
2-
use ruma::UserId;
2+
use ruma::{events::room::power_levels::UserPowerLevel, UserId};
33

44
use crate::error::{ClientError, NotYetImplemented};
55

@@ -57,16 +57,22 @@ impl TryFrom<matrix_sdk::ruma::events::room::member::MembershipState> for Member
5757
}
5858
}
5959

60+
/// Get the suggested role for the given power level.
61+
///
62+
/// Returns an error if the value of the power level is out of range for numbers
63+
/// accepted in canonical JSON.
6064
#[matrix_sdk_ffi_macros::export]
61-
pub fn suggested_role_for_power_level(power_level: i64) -> RoomMemberRole {
65+
pub fn suggested_role_for_power_level(
66+
power_level: PowerLevel,
67+
) -> Result<RoomMemberRole, ClientError> {
6268
// It's not possible to expose the constructor on the Enum through Uniffi ☹️
63-
RoomMemberRole::suggested_role_for_power_level(power_level)
69+
Ok(RoomMemberRole::suggested_role_for_power_level(power_level.try_into()?))
6470
}
6571

6672
#[matrix_sdk_ffi_macros::export]
67-
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> i64 {
73+
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> PowerLevel {
6874
// It's not possible to expose methods on an Enum through Uniffi ☹️
69-
role.suggested_power_level()
75+
role.suggested_power_level().into()
7076
}
7177

7278
/// Generates a `matrix.to` permalink to the given userID.
@@ -83,8 +89,8 @@ pub struct RoomMember {
8389
pub avatar_url: Option<String>,
8490
pub membership: MembershipState,
8591
pub is_name_ambiguous: bool,
86-
pub power_level: i64,
87-
pub normalized_power_level: i64,
92+
pub power_level: PowerLevel,
93+
pub normalized_power_level: PowerLevel,
8894
pub is_ignored: bool,
8995
pub suggested_role_for_power_level: RoomMemberRole,
9096
pub membership_change_reason: Option<String>,
@@ -100,8 +106,8 @@ impl TryFrom<SdkRoomMember> for RoomMember {
100106
avatar_url: m.avatar_url().map(|a| a.to_string()),
101107
membership: m.membership().clone().try_into()?,
102108
is_name_ambiguous: m.name_ambiguous(),
103-
power_level: m.power_level(),
104-
normalized_power_level: m.normalized_power_level(),
109+
power_level: m.power_level().into(),
110+
normalized_power_level: m.normalized_power_level().into(),
105111
is_ignored: m.is_ignored(),
106112
suggested_role_for_power_level: m.suggested_role_for_power_level(),
107113
membership_change_reason: m.event().reason().map(|s| s.to_owned()),
@@ -130,3 +136,40 @@ impl TryFrom<matrix_sdk::room::RoomMemberWithSenderInfo> for RoomMemberWithSende
130136
})
131137
}
132138
}
139+
140+
#[derive(Clone, uniffi::Enum)]
141+
pub enum PowerLevel {
142+
/// The user is a room creator and has infinite power level.
143+
///
144+
/// This power level was introduced in room version 12.
145+
Infinite,
146+
147+
/// The user has the given power level.
148+
Value { value: i64 },
149+
}
150+
151+
impl From<UserPowerLevel> for PowerLevel {
152+
fn from(value: UserPowerLevel) -> Self {
153+
match value {
154+
UserPowerLevel::Infinite => Self::Infinite,
155+
UserPowerLevel::Int(value) => Self::Value { value: value.into() },
156+
_ => unimplemented!(),
157+
}
158+
}
159+
}
160+
161+
impl TryFrom<PowerLevel> for UserPowerLevel {
162+
type Error = ClientError;
163+
164+
fn try_from(value: PowerLevel) -> Result<Self, Self::Error> {
165+
Ok(match value {
166+
PowerLevel::Infinite => Self::Infinite,
167+
PowerLevel::Value { value } => {
168+
Self::Int(value.try_into().map_err(|err| ClientError::Generic {
169+
msg: "Power level is out of range".to_owned(),
170+
details: Some(format!("{err:?}")),
171+
})?)
172+
}
173+
})
174+
}
175+
}

crates/matrix-sdk-base/src/deserialized_responses.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use ruma::{
3030
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
3131
},
3232
},
33+
room_version_rules::AuthorizationRules,
3334
serde::Raw,
3435
};
3536
use serde::Serialize;
@@ -517,10 +518,14 @@ impl MemberEvent {
517518

518519
impl SyncOrStrippedState<RoomPowerLevelsEventContent> {
519520
/// The power levels of the event.
520-
pub fn power_levels(&self) -> RoomPowerLevels {
521+
pub fn power_levels(
522+
&self,
523+
rules: &AuthorizationRules,
524+
creators: Vec<OwnedUserId>,
525+
) -> RoomPowerLevels {
521526
match self {
522-
Self::Sync(e) => e.power_levels(),
523-
Self::Stripped(e) => e.power_levels(),
527+
Self::Sync(e) => e.power_levels(rules, creators),
528+
Self::Stripped(e) => e.power_levels(rules, creators),
524529
}
525530
}
526531
}

crates/matrix-sdk-base/src/response_processors/state_events.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,7 @@ mod tests {
10801080
.add_joined_room(
10811081
JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
10821082
.add_timeline_event(room_name)
1083+
.add_state_event(StateTestEvent::Create)
10831084
.add_state_event(StateTestEvent::PowerLevels),
10841085
)
10851086
.build_sync_response();

crates/matrix-sdk-base/src/room/members.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use ruma::{
2727
presence::PresenceEvent,
2828
room::{
2929
member::{MembershipState, RoomMemberEventContent},
30-
power_levels::{PowerLevelAction, RoomPowerLevels},
30+
power_levels::{PowerLevelAction, RoomPowerLevels, UserPowerLevel},
3131
},
3232
},
3333
};
@@ -270,19 +270,27 @@ impl RoomMember {
270270
/// Get the normalized power level of this member.
271271
///
272272
/// The normalized power level depends on the maximum power level that can
273-
/// be found in a certain room, positive values are always in the range of
274-
/// 0-100.
275-
pub fn normalized_power_level(&self) -> i64 {
273+
/// be found in a certain room, positive values that are not `Infinite` are
274+
/// always in the range of 0-100.
275+
pub fn normalized_power_level(&self) -> UserPowerLevel {
276+
let UserPowerLevel::Int(power_level) = self.power_level() else {
277+
return UserPowerLevel::Infinite;
278+
};
279+
280+
let mut power_level = i64::from(power_level);
281+
276282
if self.max_power_level > 0 {
277-
(self.power_level() * 100) / self.max_power_level
278-
} else {
279-
self.power_level()
283+
power_level = (power_level * 100) / self.max_power_level;
280284
}
285+
286+
UserPowerLevel::Int(
287+
power_level.try_into().expect("normalized power level should fit in Int"),
288+
)
281289
}
282290

283291
/// Get the power level of this member.
284-
pub fn power_level(&self) -> i64 {
285-
self.power_levels.for_user(self.user_id()).into()
292+
pub fn power_level(&self) -> UserPowerLevel {
293+
self.power_levels.for_user(self.user_id())
286294
}
287295

288296
/// Whether this user can ban other users based on the power levels.

crates/matrix-sdk-base/src/room/mod.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ pub use room_info::{
4949
};
5050
use ruma::{
5151
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
52-
assign,
5352
events::{
5453
direct::OwnedDirectUserIdentifier,
5554
receipt::{Receipt, ReceiptThread, ReceiptType},
@@ -58,10 +57,9 @@ use ruma::{
5857
guest_access::GuestAccess,
5958
history_visibility::HistoryVisibility,
6059
join_rules::JoinRule,
61-
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
60+
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource},
6261
},
6362
},
64-
int,
6563
room::RoomType,
6664
};
6765
#[cfg(feature = "e2e-encryption")]
@@ -364,13 +362,16 @@ impl Room {
364362

365363
/// Get the current power levels of this room.
366364
pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
367-
Ok(self
365+
let power_levels_content = self
368366
.store
369367
.get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
370368
.await?
371369
.ok_or(Error::InsufficientData)?
372-
.deserialize()?
373-
.power_levels())
370+
.deserialize()?;
371+
let creators = self.creators().ok_or(Error::InsufficientData)?;
372+
let rules = self.inner.read().room_version_rules_or_default();
373+
374+
Ok(power_levels_content.power_levels(&rules.authorization, creators))
374375
}
375376

376377
/// Get the current power levels of this room, or a sensible default if they
@@ -380,14 +381,13 @@ impl Room {
380381
return power_levels;
381382
}
382383

383-
// As a fallback, create the default power levels of a room, with the creator at
384-
// level 100.
385-
let creator = self.creator();
386-
assign!(
387-
RoomPowerLevelsEventContent::new(),
388-
{ users: creator.into_iter().map(|user_id| (user_id, int!(100))).collect() }
384+
// As a fallback, create the default power levels of a room.
385+
let rules = self.inner.read().room_version_rules_or_default();
386+
RoomPowerLevels::new(
387+
RoomPowerLevelsSource::None,
388+
&rules.authorization,
389+
self.creators().into_iter().flatten(),
389390
)
390-
.into()
391391
}
392392

393393
/// Get the `m.room.name` of this room.

crates/matrix-sdk-base/src/room/room_info.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use ruma::{
4949
tag::{TagEventContent, TagName, Tags},
5050
},
5151
room::RoomType,
52-
room_version_rules::{RedactionRules, RoomVersionRules},
52+
room_version_rules::{AuthorizationRules, RedactionRules, RoomVersionRules},
5353
serde::Raw,
5454
};
5555
use serde::{Deserialize, Serialize};
@@ -230,7 +230,8 @@ impl BaseRoomInfo {
230230
self.tombstone = Some(t.into());
231231
}
232232
AnySyncStateEvent::RoomPowerLevels(p) => {
233-
self.max_power_level = p.power_levels().max().into();
233+
// The rules and creators do not affect the max power level.
234+
self.max_power_level = p.power_levels(&AuthorizationRules::V1, vec![]).max().into();
234235
}
235236
AnySyncStateEvent::CallMember(m) => {
236237
let Some(o_ev) = m.as_original() else {
@@ -306,7 +307,8 @@ impl BaseRoomInfo {
306307
self.tombstone = Some(t.into());
307308
}
308309
AnyStrippedStateEvent::RoomPowerLevels(p) => {
309-
self.max_power_level = p.power_levels().max().into();
310+
// The rules and creators do not affect the max power level.
311+
self.max_power_level = p.power_levels(&AuthorizationRules::V1, vec![]).max().into();
310312
}
311313
AnyStrippedStateEvent::CallMember(_) => {
312314
// Ignore stripped call state events. Rooms that are not in Joined or Left state

crates/matrix-sdk-base/src/sliding_sync.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,17 @@ mod tests {
13151315
let client = logged_in_base_client(Some(own_user_id)).await;
13161316
let room_id = room_id!("!r:e.uk");
13171317

1318+
// The room create event.
1319+
let create = json!({
1320+
"sender":"@ignacio:example.com",
1321+
"state_key":"",
1322+
"type":"m.room.create",
1323+
"event_id": "$idc",
1324+
"origin_server_ts": 12344415,
1325+
"content":{ "room_version": "11" },
1326+
"room_id": room_id,
1327+
});
1328+
13181329
// Give the current user invite or kick permissions in this room
13191330
let power_levels = json!({
13201331
"sender":"@alice:example.com",
@@ -1340,7 +1351,10 @@ mod tests {
13401351
// When the sliding sync response contains a timeline
13411352
let events = &[knock_event];
13421353
let mut room = room_with_timeline(events);
1343-
room.required_state.push(Raw::new(&power_levels).unwrap().cast_unchecked());
1354+
room.required_state.extend([
1355+
Raw::new(&create).unwrap().cast_unchecked(),
1356+
Raw::new(&power_levels).unwrap().cast_unchecked(),
1357+
]);
13441358
let response = response_with_room(room_id, room);
13451359
client
13461360
.process_sliding_sync(&response, &RequestedRequiredStates::default())

crates/matrix-sdk-base/src/store/integration_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use ruma::{
3030
},
3131
},
3232
owned_event_id, owned_mxc_uri, room_id,
33+
room_version_rules::AuthorizationRules,
3334
serde::Raw,
3435
uint, user_id,
3536
};
@@ -1961,7 +1962,7 @@ fn first_receipt_event_id() -> &'static EventId {
19611962
}
19621963

19631964
fn power_level_event() -> Raw<AnySyncStateEvent> {
1964-
let content = RoomPowerLevelsEventContent::default();
1965+
let content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
19651966

19661967
let event = json!({
19671968
"event_id": "$h29iv0s8:example.com",

crates/matrix-sdk-base/src/store/mod.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use std::{
3232

3333
use eyeball_im::{Vector, VectorDiff};
3434
use futures_util::Stream;
35+
use matrix_sdk_common::ROOM_VERSION_RULES_FALLBACK;
3536
use once_cell::sync::OnceCell;
3637

3738
#[cfg(any(test, feature = "testing"))]
@@ -54,6 +55,7 @@ use ruma::{
5455
presence::PresenceEvent,
5556
receipt::ReceiptEventContent,
5657
room::{
58+
create::RoomCreateEventContent,
5759
member::{RoomMemberEventContent, StrippedRoomMemberEvent},
5860
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
5961
redaction::SyncRoomRedactionEvent,
@@ -66,7 +68,7 @@ use tokio::sync::{Mutex, RwLock, broadcast};
6668
use tracing::warn;
6769

6870
use crate::{
69-
MinimalRoomMemberEvent, Room, RoomStateFilter, SessionMeta,
71+
MinimalRoomMemberEvent, Room, RoomCreateWithCreatorEventContent, RoomStateFilter, SessionMeta,
7072
deserialized_responses::DisplayName,
7173
event_cache::store as event_cache_store,
7274
room::{RoomInfo, RoomInfoNotableUpdate, RoomState},
@@ -641,16 +643,28 @@ impl StateChanges {
641643
self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
642644
}
643645

646+
/// Get the create event for the given room from an event contained in these
647+
/// `StateChanges`, if any.
648+
pub(crate) fn create(&self, room_id: &RoomId) -> Option<RoomCreateWithCreatorEventContent> {
649+
self.any_state_static_for_key::<RoomCreateEventContent, _>(room_id, &EmptyStateKey)
650+
.map(|event| {
651+
RoomCreateWithCreatorEventContent::from_event_content(event.content, event.sender)
652+
})
653+
// Fallback to the content in the room info.
654+
.or_else(|| self.room_infos.get(room_id)?.create().cloned())
655+
}
656+
644657
/// Get the power levels for the given room from an event contained in these
645658
/// `StateChanges`, if any.
646659
pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
647-
Some(
648-
self.any_state_static_for_key::<RoomPowerLevelsEventContent, _>(
649-
room_id,
650-
&EmptyStateKey,
651-
)?
652-
.power_levels(),
653-
)
660+
let power_levels_content = self
661+
.any_state_static_for_key::<RoomPowerLevelsEventContent, _>(room_id, &EmptyStateKey)?;
662+
663+
let create_content = self.create(room_id)?;
664+
let rules = create_content.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
665+
let creators = create_content.creators();
666+
667+
Some(power_levels_content.power_levels(&rules.authorization, creators))
654668
}
655669
}
656670

0 commit comments

Comments
 (0)