1use std::{cmp::max, collections::BTreeMap};
6
7use js_int::{int, Int};
8use ruma_common::{
9 power_levels::{default_power_level, NotificationPowerLevels},
10 push::PushConditionPowerLevelsCtx,
11 room_version_rules::RedactionRules,
12 OwnedUserId, UserId,
13};
14use ruma_macros::EventContent;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 EmptyStateKey, EventContent, MessageLikeEventType, RedactContent, RedactedStateEventContent,
19 StateEventType, StaticEventContent, TimelineEventType,
20};
21
22#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
26#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
27#[ruma_event(type = "m.room.power_levels", kind = State, state_key_type = EmptyStateKey, custom_redacted)]
28pub struct RoomPowerLevelsEventContent {
29 #[serde(
31 default = "default_power_level",
32 skip_serializing_if = "is_default_power_level",
33 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
34 )]
35 pub ban: Int,
36
37 #[serde(
41 default,
42 skip_serializing_if = "BTreeMap::is_empty",
43 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
44 )]
45 pub events: BTreeMap<TimelineEventType, Int>,
46
47 #[serde(
49 default,
50 skip_serializing_if = "ruma_common::serde::is_default",
51 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
52 )]
53 pub events_default: Int,
54
55 #[serde(
57 default,
58 skip_serializing_if = "ruma_common::serde::is_default",
59 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
60 )]
61 pub invite: Int,
62
63 #[serde(
65 default = "default_power_level",
66 skip_serializing_if = "is_default_power_level",
67 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
68 )]
69 pub kick: Int,
70
71 #[serde(
73 default = "default_power_level",
74 skip_serializing_if = "is_default_power_level",
75 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
76 )]
77 pub redact: Int,
78
79 #[serde(
81 default = "default_power_level",
82 skip_serializing_if = "is_default_power_level",
83 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
84 )]
85 pub state_default: Int,
86
87 #[serde(
91 default,
92 skip_serializing_if = "BTreeMap::is_empty",
93 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
94 )]
95 pub users: BTreeMap<OwnedUserId, Int>,
96
97 #[serde(
99 default,
100 skip_serializing_if = "ruma_common::serde::is_default",
101 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
102 )]
103 pub users_default: Int,
104
105 #[serde(default, skip_serializing_if = "NotificationPowerLevels::is_default")]
109 pub notifications: NotificationPowerLevels,
110}
111
112impl RoomPowerLevelsEventContent {
113 pub fn new() -> Self {
115 Self {
118 ban: default_power_level(),
119 events: BTreeMap::new(),
120 events_default: int!(0),
121 invite: int!(0),
122 kick: default_power_level(),
123 redact: default_power_level(),
124 state_default: default_power_level(),
125 users: BTreeMap::new(),
126 users_default: int!(0),
127 notifications: NotificationPowerLevels::default(),
128 }
129 }
130}
131
132impl Default for RoomPowerLevelsEventContent {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl RedactContent for RoomPowerLevelsEventContent {
139 type Redacted = RedactedRoomPowerLevelsEventContent;
140
141 fn redact(self, rules: &RedactionRules) -> Self::Redacted {
142 let Self {
143 ban,
144 events,
145 events_default,
146 invite,
147 kick,
148 redact,
149 state_default,
150 users,
151 users_default,
152 ..
153 } = self;
154
155 let invite = if rules.keep_room_power_levels_invite { invite } else { int!(0) };
156
157 RedactedRoomPowerLevelsEventContent {
158 ban,
159 events,
160 events_default,
161 invite,
162 kick,
163 redact,
164 state_default,
165 users,
166 users_default,
167 }
168 }
169}
170
171#[allow(clippy::trivially_copy_pass_by_ref)]
173fn is_default_power_level(l: &Int) -> bool {
174 *l == int!(50)
175}
176
177impl RoomPowerLevelsEvent {
178 pub fn power_levels(&self) -> RoomPowerLevels {
180 match self {
181 Self::Original(ev) => ev.content.clone().into(),
182 Self::Redacted(ev) => ev.content.clone().into(),
183 }
184 }
185}
186
187impl SyncRoomPowerLevelsEvent {
188 pub fn power_levels(&self) -> RoomPowerLevels {
190 match self {
191 Self::Original(ev) => ev.content.clone().into(),
192 Self::Redacted(ev) => ev.content.clone().into(),
193 }
194 }
195}
196
197impl StrippedRoomPowerLevelsEvent {
198 pub fn power_levels(&self) -> RoomPowerLevels {
200 self.content.clone().into()
201 }
202}
203
204#[derive(Clone, Debug, Deserialize, Serialize)]
206#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
207pub struct RedactedRoomPowerLevelsEventContent {
208 #[serde(
210 default = "default_power_level",
211 skip_serializing_if = "is_default_power_level",
212 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
213 )]
214 pub ban: Int,
215
216 #[serde(
220 default,
221 skip_serializing_if = "BTreeMap::is_empty",
222 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
223 )]
224 pub events: BTreeMap<TimelineEventType, Int>,
225
226 #[serde(
228 default,
229 skip_serializing_if = "ruma_common::serde::is_default",
230 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
231 )]
232 pub events_default: Int,
233
234 #[serde(
239 default,
240 skip_serializing_if = "ruma_common::serde::is_default",
241 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
242 )]
243 pub invite: Int,
244
245 #[serde(
247 default = "default_power_level",
248 skip_serializing_if = "is_default_power_level",
249 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
250 )]
251 pub kick: Int,
252
253 #[serde(
255 default = "default_power_level",
256 skip_serializing_if = "is_default_power_level",
257 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
258 )]
259 pub redact: Int,
260
261 #[serde(
263 default = "default_power_level",
264 skip_serializing_if = "is_default_power_level",
265 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
266 )]
267 pub state_default: Int,
268
269 #[serde(
273 default,
274 skip_serializing_if = "BTreeMap::is_empty",
275 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
276 )]
277 pub users: BTreeMap<OwnedUserId, Int>,
278
279 #[serde(
281 default,
282 skip_serializing_if = "ruma_common::serde::is_default",
283 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
284 )]
285 pub users_default: Int,
286}
287
288impl EventContent for RedactedRoomPowerLevelsEventContent {
289 type EventType = StateEventType;
290
291 fn event_type(&self) -> Self::EventType {
292 StateEventType::RoomPowerLevels
293 }
294}
295
296impl StaticEventContent for RedactedRoomPowerLevelsEventContent {
297 const TYPE: &'static str = "m.room.power_levels";
298}
299
300impl RedactedStateEventContent for RedactedRoomPowerLevelsEventContent {
301 type StateKey = EmptyStateKey;
302}
303
304#[derive(Clone, Debug)]
312#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
313pub struct RoomPowerLevels {
314 pub ban: Int,
316
317 pub events: BTreeMap<TimelineEventType, Int>,
321
322 pub events_default: Int,
324
325 pub invite: Int,
327
328 pub kick: Int,
330
331 pub redact: Int,
333
334 pub state_default: Int,
336
337 pub users: BTreeMap<OwnedUserId, Int>,
341
342 pub users_default: Int,
344
345 pub notifications: NotificationPowerLevels,
349}
350
351impl RoomPowerLevels {
352 pub fn for_user(&self, user_id: &UserId) -> Int {
354 self.users.get(user_id).map_or(self.users_default, |pl| *pl)
355 }
356
357 pub fn for_action(&self, action: PowerLevelAction) -> Int {
359 match action {
360 PowerLevelAction::Ban => self.ban,
361 PowerLevelAction::Unban => self.ban.max(self.kick),
362 PowerLevelAction::Invite => self.invite,
363 PowerLevelAction::Kick => self.kick,
364 PowerLevelAction::RedactOwn => self.for_message(MessageLikeEventType::RoomRedaction),
365 PowerLevelAction::RedactOther => {
366 self.redact.max(self.for_message(MessageLikeEventType::RoomRedaction))
367 }
368 PowerLevelAction::SendMessage(msg_type) => self.for_message(msg_type),
369 PowerLevelAction::SendState(state_type) => self.for_state(state_type),
370 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room) => {
371 self.notifications.room
372 }
373 }
374 }
375
376 pub fn for_message(&self, msg_type: MessageLikeEventType) -> Int {
378 self.events.get(&msg_type.into()).copied().unwrap_or(self.events_default)
379 }
380
381 pub fn for_state(&self, state_type: StateEventType) -> Int {
383 self.events.get(&state_type.into()).copied().unwrap_or(self.state_default)
384 }
385
386 pub fn user_can_ban(&self, user_id: &UserId) -> bool {
390 self.for_user(user_id) >= self.ban
391 }
392
393 pub fn user_can_ban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
401 let acting_pl = self.for_user(acting_user_id);
402 let target_pl = self.for_user(target_user_id);
403 acting_pl >= self.ban && target_pl < acting_pl
404 }
405
406 pub fn user_can_unban(&self, user_id: &UserId) -> bool {
412 let pl = self.for_user(user_id);
413 pl >= self.ban && pl >= self.kick
414 }
415
416 pub fn user_can_unban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
426 let acting_pl = self.for_user(acting_user_id);
427 let target_pl = self.for_user(target_user_id);
428 acting_pl >= self.ban && acting_pl >= self.kick && target_pl < acting_pl
429 }
430
431 pub fn user_can_invite(&self, user_id: &UserId) -> bool {
435 self.for_user(user_id) >= self.invite
436 }
437
438 pub fn user_can_kick(&self, user_id: &UserId) -> bool {
442 self.for_user(user_id) >= self.kick
443 }
444
445 pub fn user_can_kick_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
453 let acting_pl = self.for_user(acting_user_id);
454 let target_pl = self.for_user(target_user_id);
455 acting_pl >= self.kick && target_pl < acting_pl
456 }
457
458 pub fn user_can_redact_own_event(&self, user_id: &UserId) -> bool {
462 self.user_can_send_message(user_id, MessageLikeEventType::RoomRedaction)
463 }
464
465 pub fn user_can_redact_event_of_other(&self, user_id: &UserId) -> bool {
469 self.user_can_redact_own_event(user_id) && self.for_user(user_id) >= self.redact
470 }
471
472 pub fn user_can_send_message(&self, user_id: &UserId, msg_type: MessageLikeEventType) -> bool {
476 self.for_user(user_id) >= self.for_message(msg_type)
477 }
478
479 pub fn user_can_send_state(&self, user_id: &UserId, state_type: StateEventType) -> bool {
483 self.for_user(user_id) >= self.for_state(state_type)
484 }
485
486 pub fn user_can_trigger_room_notification(&self, user_id: &UserId) -> bool {
491 self.for_user(user_id) >= self.notifications.room
492 }
493
494 pub fn user_can_change_user_power_level(
499 &self,
500 acting_user_id: &UserId,
501 target_user_id: &UserId,
502 ) -> bool {
503 if !self.user_can_send_state(acting_user_id, StateEventType::RoomPowerLevels) {
505 return false;
506 }
507
508 if acting_user_id == target_user_id {
510 return true;
511 }
512
513 if let Some(target_pl) = self.users.get(target_user_id).copied() {
516 self.for_user(acting_user_id) > target_pl
517 } else {
518 true
519 }
520 }
521
522 pub fn user_can_do(&self, user_id: &UserId, action: PowerLevelAction) -> bool {
524 match action {
525 PowerLevelAction::Ban => self.user_can_ban(user_id),
526 PowerLevelAction::Unban => self.user_can_unban(user_id),
527 PowerLevelAction::Invite => self.user_can_invite(user_id),
528 PowerLevelAction::Kick => self.user_can_kick(user_id),
529 PowerLevelAction::RedactOwn => self.user_can_redact_own_event(user_id),
530 PowerLevelAction::RedactOther => self.user_can_redact_event_of_other(user_id),
531 PowerLevelAction::SendMessage(message_type) => {
532 self.user_can_send_message(user_id, message_type)
533 }
534 PowerLevelAction::SendState(state_type) => {
535 self.user_can_send_state(user_id, state_type)
536 }
537 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room) => {
538 self.user_can_trigger_room_notification(user_id)
539 }
540 }
541 }
542
543 pub fn user_can_do_to_user(
546 &self,
547 acting_user_id: &UserId,
548 target_user_id: &UserId,
549 action: PowerLevelUserAction,
550 ) -> bool {
551 match action {
552 PowerLevelUserAction::Ban => self.user_can_ban_user(acting_user_id, target_user_id),
553 PowerLevelUserAction::Unban => self.user_can_unban_user(acting_user_id, target_user_id),
554 PowerLevelUserAction::Invite => self.user_can_invite(acting_user_id),
555 PowerLevelUserAction::Kick => self.user_can_kick_user(acting_user_id, target_user_id),
556 PowerLevelUserAction::ChangePowerLevel => {
557 self.user_can_change_user_power_level(acting_user_id, target_user_id)
558 }
559 }
560 }
561
562 pub fn max(&self) -> Int {
564 self.users.values().fold(self.users_default, |max_pl, user_pl| max(max_pl, *user_pl))
565 }
566}
567
568impl From<RoomPowerLevelsEventContent> for RoomPowerLevels {
569 fn from(c: RoomPowerLevelsEventContent) -> Self {
570 Self {
571 ban: c.ban,
572 events: c.events,
573 events_default: c.events_default,
574 invite: c.invite,
575 kick: c.kick,
576 redact: c.redact,
577 state_default: c.state_default,
578 users: c.users,
579 users_default: c.users_default,
580 notifications: c.notifications,
581 }
582 }
583}
584
585impl From<RedactedRoomPowerLevelsEventContent> for RoomPowerLevels {
586 fn from(c: RedactedRoomPowerLevelsEventContent) -> Self {
587 Self {
588 ban: c.ban,
589 events: c.events,
590 events_default: c.events_default,
591 invite: c.invite,
592 kick: c.kick,
593 redact: c.redact,
594 state_default: c.state_default,
595 users: c.users,
596 users_default: c.users_default,
597 notifications: NotificationPowerLevels::default(),
598 }
599 }
600}
601
602impl From<RoomPowerLevels> for RoomPowerLevelsEventContent {
603 fn from(c: RoomPowerLevels) -> Self {
604 Self {
605 ban: c.ban,
606 events: c.events,
607 events_default: c.events_default,
608 invite: c.invite,
609 kick: c.kick,
610 redact: c.redact,
611 state_default: c.state_default,
612 users: c.users,
613 users_default: c.users_default,
614 notifications: c.notifications,
615 }
616 }
617}
618
619impl From<RoomPowerLevels> for PushConditionPowerLevelsCtx {
620 fn from(c: RoomPowerLevels) -> Self {
621 Self { users: c.users, users_default: c.users_default, notifications: c.notifications }
622 }
623}
624
625#[derive(Clone, Debug, PartialEq, Eq)]
627#[non_exhaustive]
628pub enum PowerLevelAction {
629 Ban,
631
632 Unban,
634
635 Invite,
637
638 Kick,
640
641 RedactOwn,
643
644 RedactOther,
646
647 SendMessage(MessageLikeEventType),
649
650 SendState(StateEventType),
652
653 TriggerNotification(NotificationPowerLevelType),
655}
656
657#[derive(Clone, Debug, PartialEq, Eq)]
659#[non_exhaustive]
660pub enum NotificationPowerLevelType {
661 Room,
663}
664
665#[derive(Clone, Debug, PartialEq, Eq)]
667#[non_exhaustive]
668pub enum PowerLevelUserAction {
669 Ban,
671
672 Unban,
674
675 Invite,
677
678 Kick,
680
681 ChangePowerLevel,
683}
684
685#[cfg(test)]
686mod tests {
687 use std::collections::BTreeMap;
688
689 use assign::assign;
690 use js_int::int;
691 use maplit::btreemap;
692 use ruma_common::user_id;
693 use serde_json::{json, to_value as to_json_value};
694
695 use super::{default_power_level, NotificationPowerLevels, RoomPowerLevelsEventContent};
696
697 #[test]
698 fn serialization_with_optional_fields_as_none() {
699 let default = default_power_level();
700
701 let power_levels = RoomPowerLevelsEventContent {
702 ban: default,
703 events: BTreeMap::new(),
704 events_default: int!(0),
705 invite: int!(0),
706 kick: default,
707 redact: default,
708 state_default: default,
709 users: BTreeMap::new(),
710 users_default: int!(0),
711 notifications: NotificationPowerLevels::default(),
712 };
713
714 let actual = to_json_value(&power_levels).unwrap();
715 let expected = json!({});
716
717 assert_eq!(actual, expected);
718 }
719
720 #[test]
721 fn serialization_with_all_fields() {
722 let user = user_id!("@carl:example.com");
723 let power_levels_event = RoomPowerLevelsEventContent {
724 ban: int!(23),
725 events: btreemap! {
726 "m.dummy".into() => int!(23)
727 },
728 events_default: int!(23),
729 invite: int!(23),
730 kick: int!(23),
731 redact: int!(23),
732 state_default: int!(23),
733 users: btreemap! {
734 user.to_owned() => int!(23)
735 },
736 users_default: int!(23),
737 notifications: assign!(NotificationPowerLevels::new(), { room: int!(23) }),
738 };
739
740 let actual = to_json_value(&power_levels_event).unwrap();
741 let expected = json!({
742 "ban": 23,
743 "events": {
744 "m.dummy": 23
745 },
746 "events_default": 23,
747 "invite": 23,
748 "kick": 23,
749 "redact": 23,
750 "state_default": 23,
751 "users": {
752 "@carl:example.com": 23
753 },
754 "users_default": 23,
755 "notifications": {
756 "room": 23
757 },
758 });
759
760 assert_eq!(actual, expected);
761 }
762}