Skip to content

Commit 5822d88

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

File tree

20 files changed

+275
-102
lines changed

20 files changed

+275
-102
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
@@ -29,6 +29,7 @@ use ruma::{
2929
PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
3030
StateEventContent, StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
3131
},
32+
room_version_rules::AuthorizationRules,
3233
serde::Raw,
3334
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
3435
};
@@ -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
@@ -25,7 +25,7 @@ use ruma::{
2525
presence::PresenceEvent,
2626
room::{
2727
member::{MembershipState, RoomMemberEventContent},
28-
power_levels::{PowerLevelAction, RoomPowerLevels},
28+
power_levels::{PowerLevelAction, RoomPowerLevels, UserPowerLevel},
2929
},
3030
MessageLikeEventType, StateEventType,
3131
},
@@ -274,19 +274,27 @@ impl RoomMember {
274274
/// Get the normalized power level of this member.
275275
///
276276
/// The normalized power level depends on the maximum power level that can
277-
/// be found in a certain room, positive values are always in the range of
278-
/// 0-100.
279-
pub fn normalized_power_level(&self) -> i64 {
277+
/// be found in a certain room, positive values that are not `Infinite` are
278+
/// always in the range of 0-100.
279+
pub fn normalized_power_level(&self) -> UserPowerLevel {
280+
let UserPowerLevel::Int(power_level) = self.power_level() else {
281+
return UserPowerLevel::Infinite;
282+
};
283+
284+
let mut power_level = i64::from(power_level);
285+
280286
if self.max_power_level > 0 {
281-
(self.power_level() * 100) / self.max_power_level
282-
} else {
283-
self.power_level()
287+
power_level = (power_level * 100) / self.max_power_level;
284288
}
289+
290+
UserPowerLevel::Int(
291+
power_level.try_into().expect("normalized power level should fit in Int"),
292+
)
285293
}
286294

287295
/// Get the power level of this member.
288-
pub fn power_level(&self) -> i64 {
289-
self.power_levels.for_user(self.user_id()).into()
296+
pub fn power_level(&self) -> UserPowerLevel {
297+
self.power_levels.for_user(self.user_id())
290298
}
291299

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

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ pub use room_info::{
4747
apply_redaction, BaseRoomInfo, InviteAcceptanceDetails, RoomInfo, RoomInfoNotableUpdate,
4848
RoomInfoNotableUpdateReasons,
4949
};
50+
#[cfg(feature = "e2e-encryption")]
51+
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
5052
use ruma::{
51-
assign,
5253
events::{
5354
direct::OwnedDirectUserIdentifier,
5455
receipt::{Receipt, ReceiptThread, ReceiptType},
@@ -57,15 +58,12 @@ use ruma::{
5758
guest_access::GuestAccess,
5859
history_visibility::HistoryVisibility,
5960
join_rules::JoinRule,
60-
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
61+
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource},
6162
},
6263
},
63-
int,
6464
room::RoomType,
6565
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
6666
};
67-
#[cfg(feature = "e2e-encryption")]
68-
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
6967
use serde::{Deserialize, Serialize};
7068
pub use state::{RoomState, RoomStateFilter};
7169
pub(crate) use tags::RoomNotableTags;
@@ -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
@@ -47,7 +47,7 @@ use ruma::{
4747
RedactedStateEventContent, StateEventType, StaticStateEventContent, SyncStateEvent,
4848
},
4949
room::RoomType,
50-
room_version_rules::{RedactionRules, RoomVersionRules},
50+
room_version_rules::{AuthorizationRules, RedactionRules, RoomVersionRules},
5151
serde::Raw,
5252
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
5353
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
@@ -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
@@ -1302,6 +1302,17 @@ mod tests {
13021302
let client = logged_in_base_client(Some(own_user_id)).await;
13031303
let room_id = room_id!("!r:e.uk");
13041304

1305+
// The room create event.
1306+
let create = json!({
1307+
"sender":"@ignacio:example.com",
1308+
"state_key":"",
1309+
"type":"m.room.create",
1310+
"event_id": "$idc",
1311+
"origin_server_ts": 12344415,
1312+
"content":{ "room_version": "11" },
1313+
"room_id": room_id,
1314+
});
1315+
13051316
// Give the current user invite or kick permissions in this room
13061317
let power_levels = json!({
13071318
"sender":"@alice:example.com",
@@ -1327,7 +1338,10 @@ mod tests {
13271338
// When the sliding sync response contains a timeline
13281339
let events = &[knock_event];
13291340
let mut room = room_with_timeline(events);
1330-
room.required_state.push(Raw::new(&power_levels).unwrap().cast_unchecked());
1341+
room.required_state.extend([
1342+
Raw::new(&create).unwrap().cast_unchecked(),
1343+
Raw::new(&power_levels).unwrap().cast_unchecked(),
1344+
]);
13311345
let response = response_with_room(room_id, room);
13321346
client
13331347
.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
@@ -29,6 +29,7 @@ use ruma::{
2929
RoomAccountDataEventType, StateEventType, SyncStateEvent,
3030
},
3131
owned_event_id, owned_mxc_uri, room_id,
32+
room_version_rules::AuthorizationRules,
3233
serde::Raw,
3334
uint, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId,
3435
TransactionId, UserId,
@@ -1944,7 +1945,7 @@ fn first_receipt_event_id() -> &'static EventId {
19441945
}
19451946

19461947
fn power_level_event() -> Raw<AnySyncStateEvent> {
1947-
let content = RoomPowerLevelsEventContent::default();
1948+
let content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
19481949

19491950
let event = json!({
19501951
"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"))]
@@ -49,6 +50,7 @@ use ruma::{
4950
presence::PresenceEvent,
5051
receipt::ReceiptEventContent,
5152
room::{
53+
create::RoomCreateEventContent,
5254
member::{RoomMemberEventContent, StrippedRoomMemberEvent},
5355
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
5456
redaction::SyncRoomRedactionEvent,
@@ -69,7 +71,7 @@ use crate::{
6971
deserialized_responses::DisplayName,
7072
event_cache::store as event_cache_store,
7173
room::{RoomInfo, RoomInfoNotableUpdate, RoomState},
72-
MinimalRoomMemberEvent, Room, RoomStateFilter, SessionMeta,
74+
MinimalRoomMemberEvent, Room, RoomCreateWithCreatorEventContent, RoomStateFilter, SessionMeta,
7375
};
7476

7577
pub(crate) mod ambiguity_map;
@@ -639,16 +641,28 @@ impl StateChanges {
639641
self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
640642
}
641643

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

0 commit comments

Comments
 (0)