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 OwnedUserId, RoomVersionId, UserId,
12};
13use ruma_macros::EventContent;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 EmptyStateKey, EventContent, MessageLikeEventType, RedactContent, RedactedStateEventContent,
18 StateEventType, StaticEventContent, TimelineEventType,
19};
20
21#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
25#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
26#[ruma_event(type = "m.room.power_levels", kind = State, state_key_type = EmptyStateKey, custom_redacted)]
27pub struct RoomPowerLevelsEventContent {
28 #[serde(
30 default = "default_power_level",
31 skip_serializing_if = "is_default_power_level",
32 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
33 )]
34 pub ban: Int,
35
36 #[serde(
40 default,
41 skip_serializing_if = "BTreeMap::is_empty",
42 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
43 )]
44 pub events: BTreeMap<TimelineEventType, Int>,
45
46 #[serde(
48 default,
49 skip_serializing_if = "ruma_common::serde::is_default",
50 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
51 )]
52 pub events_default: Int,
53
54 #[serde(
56 default,
57 skip_serializing_if = "ruma_common::serde::is_default",
58 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
59 )]
60 pub invite: Int,
61
62 #[serde(
64 default = "default_power_level",
65 skip_serializing_if = "is_default_power_level",
66 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
67 )]
68 pub kick: Int,
69
70 #[serde(
72 default = "default_power_level",
73 skip_serializing_if = "is_default_power_level",
74 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
75 )]
76 pub redact: Int,
77
78 #[serde(
80 default = "default_power_level",
81 skip_serializing_if = "is_default_power_level",
82 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
83 )]
84 pub state_default: Int,
85
86 #[serde(
90 default,
91 skip_serializing_if = "BTreeMap::is_empty",
92 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
93 )]
94 pub users: BTreeMap<OwnedUserId, Int>,
95
96 #[serde(
98 default,
99 skip_serializing_if = "ruma_common::serde::is_default",
100 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
101 )]
102 pub users_default: Int,
103
104 #[serde(default, skip_serializing_if = "NotificationPowerLevels::is_default")]
108 pub notifications: NotificationPowerLevels,
109}
110
111impl RoomPowerLevelsEventContent {
112 pub fn new() -> Self {
114 Self {
117 ban: default_power_level(),
118 events: BTreeMap::new(),
119 events_default: int!(0),
120 invite: int!(0),
121 kick: default_power_level(),
122 redact: default_power_level(),
123 state_default: default_power_level(),
124 users: BTreeMap::new(),
125 users_default: int!(0),
126 notifications: NotificationPowerLevels::default(),
127 }
128 }
129}
130
131impl Default for RoomPowerLevelsEventContent {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137impl RedactContent for RoomPowerLevelsEventContent {
138 type Redacted = RedactedRoomPowerLevelsEventContent;
139
140 fn redact(self, version: &RoomVersionId) -> Self::Redacted {
141 let Self {
142 ban,
143 events,
144 events_default,
145 invite,
146 kick,
147 redact,
148 state_default,
149 users,
150 users_default,
151 ..
152 } = self;
153
154 let invite = match version {
155 RoomVersionId::V1
156 | RoomVersionId::V2
157 | RoomVersionId::V3
158 | RoomVersionId::V4
159 | RoomVersionId::V5
160 | RoomVersionId::V6
161 | RoomVersionId::V7
162 | RoomVersionId::V8
163 | RoomVersionId::V9
164 | RoomVersionId::V10 => int!(0),
165 _ => invite,
166 };
167
168 RedactedRoomPowerLevelsEventContent {
169 ban,
170 events,
171 events_default,
172 invite,
173 kick,
174 redact,
175 state_default,
176 users,
177 users_default,
178 }
179 }
180}
181
182#[allow(clippy::trivially_copy_pass_by_ref)]
184fn is_default_power_level(l: &Int) -> bool {
185 *l == int!(50)
186}
187
188impl RoomPowerLevelsEvent {
189 pub fn power_levels(&self) -> RoomPowerLevels {
191 match self {
192 Self::Original(ev) => ev.content.clone().into(),
193 Self::Redacted(ev) => ev.content.clone().into(),
194 }
195 }
196}
197
198impl SyncRoomPowerLevelsEvent {
199 pub fn power_levels(&self) -> RoomPowerLevels {
201 match self {
202 Self::Original(ev) => ev.content.clone().into(),
203 Self::Redacted(ev) => ev.content.clone().into(),
204 }
205 }
206}
207
208impl StrippedRoomPowerLevelsEvent {
209 pub fn power_levels(&self) -> RoomPowerLevels {
211 self.content.clone().into()
212 }
213}
214
215#[derive(Clone, Debug, Deserialize, Serialize)]
217#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
218pub struct RedactedRoomPowerLevelsEventContent {
219 #[serde(
221 default = "default_power_level",
222 skip_serializing_if = "is_default_power_level",
223 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
224 )]
225 pub ban: Int,
226
227 #[serde(
231 default,
232 skip_serializing_if = "BTreeMap::is_empty",
233 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
234 )]
235 pub events: BTreeMap<TimelineEventType, Int>,
236
237 #[serde(
239 default,
240 skip_serializing_if = "ruma_common::serde::is_default",
241 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
242 )]
243 pub events_default: Int,
244
245 #[serde(
250 default,
251 skip_serializing_if = "ruma_common::serde::is_default",
252 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
253 )]
254 pub invite: Int,
255
256 #[serde(
258 default = "default_power_level",
259 skip_serializing_if = "is_default_power_level",
260 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
261 )]
262 pub kick: Int,
263
264 #[serde(
266 default = "default_power_level",
267 skip_serializing_if = "is_default_power_level",
268 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
269 )]
270 pub redact: Int,
271
272 #[serde(
274 default = "default_power_level",
275 skip_serializing_if = "is_default_power_level",
276 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
277 )]
278 pub state_default: Int,
279
280 #[serde(
284 default,
285 skip_serializing_if = "BTreeMap::is_empty",
286 deserialize_with = "ruma_common::serde::btreemap_deserialize_v1_powerlevel_values"
287 )]
288 pub users: BTreeMap<OwnedUserId, Int>,
289
290 #[serde(
292 default,
293 skip_serializing_if = "ruma_common::serde::is_default",
294 deserialize_with = "ruma_common::serde::deserialize_v1_powerlevel"
295 )]
296 pub users_default: Int,
297}
298
299impl EventContent for RedactedRoomPowerLevelsEventContent {
300 type EventType = StateEventType;
301
302 fn event_type(&self) -> Self::EventType {
303 StateEventType::RoomPowerLevels
304 }
305}
306
307impl StaticEventContent for RedactedRoomPowerLevelsEventContent {
308 const TYPE: &'static str = "m.room.power_levels";
309}
310
311impl RedactedStateEventContent for RedactedRoomPowerLevelsEventContent {
312 type StateKey = EmptyStateKey;
313}
314
315#[derive(Clone, Debug)]
323#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
324pub struct RoomPowerLevels {
325 pub ban: Int,
327
328 pub events: BTreeMap<TimelineEventType, Int>,
332
333 pub events_default: Int,
335
336 pub invite: Int,
338
339 pub kick: Int,
341
342 pub redact: Int,
344
345 pub state_default: Int,
347
348 pub users: BTreeMap<OwnedUserId, Int>,
352
353 pub users_default: Int,
355
356 pub notifications: NotificationPowerLevels,
360}
361
362impl RoomPowerLevels {
363 pub fn for_user(&self, user_id: &UserId) -> Int {
365 self.users.get(user_id).map_or(self.users_default, |pl| *pl)
366 }
367
368 pub fn for_action(&self, action: PowerLevelAction) -> Int {
370 match action {
371 PowerLevelAction::Ban => self.ban,
372 PowerLevelAction::Unban => self.ban.max(self.kick),
373 PowerLevelAction::Invite => self.invite,
374 PowerLevelAction::Kick => self.kick,
375 PowerLevelAction::RedactOwn => self.for_message(MessageLikeEventType::RoomRedaction),
376 PowerLevelAction::RedactOther => {
377 self.redact.max(self.for_message(MessageLikeEventType::RoomRedaction))
378 }
379 PowerLevelAction::SendMessage(msg_type) => self.for_message(msg_type),
380 PowerLevelAction::SendState(state_type) => self.for_state(state_type),
381 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room) => {
382 self.notifications.room
383 }
384 }
385 }
386
387 pub fn for_message(&self, msg_type: MessageLikeEventType) -> Int {
389 self.events.get(&msg_type.into()).copied().unwrap_or(self.events_default)
390 }
391
392 pub fn for_state(&self, state_type: StateEventType) -> Int {
394 self.events.get(&state_type.into()).copied().unwrap_or(self.state_default)
395 }
396
397 pub fn user_can_ban(&self, user_id: &UserId) -> bool {
401 self.for_user(user_id) >= self.ban
402 }
403
404 pub fn user_can_ban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
412 let acting_pl = self.for_user(acting_user_id);
413 let target_pl = self.for_user(target_user_id);
414 acting_pl >= self.ban && target_pl < acting_pl
415 }
416
417 pub fn user_can_unban(&self, user_id: &UserId) -> bool {
423 let pl = self.for_user(user_id);
424 pl >= self.ban && pl >= self.kick
425 }
426
427 pub fn user_can_unban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
437 let acting_pl = self.for_user(acting_user_id);
438 let target_pl = self.for_user(target_user_id);
439 acting_pl >= self.ban && acting_pl >= self.kick && target_pl < acting_pl
440 }
441
442 pub fn user_can_invite(&self, user_id: &UserId) -> bool {
446 self.for_user(user_id) >= self.invite
447 }
448
449 pub fn user_can_kick(&self, user_id: &UserId) -> bool {
453 self.for_user(user_id) >= self.kick
454 }
455
456 pub fn user_can_kick_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
464 let acting_pl = self.for_user(acting_user_id);
465 let target_pl = self.for_user(target_user_id);
466 acting_pl >= self.kick && target_pl < acting_pl
467 }
468
469 pub fn user_can_redact_own_event(&self, user_id: &UserId) -> bool {
473 self.user_can_send_message(user_id, MessageLikeEventType::RoomRedaction)
474 }
475
476 pub fn user_can_redact_event_of_other(&self, user_id: &UserId) -> bool {
480 self.user_can_redact_own_event(user_id) && self.for_user(user_id) >= self.redact
481 }
482
483 pub fn user_can_send_message(&self, user_id: &UserId, msg_type: MessageLikeEventType) -> bool {
487 self.for_user(user_id) >= self.for_message(msg_type)
488 }
489
490 pub fn user_can_send_state(&self, user_id: &UserId, state_type: StateEventType) -> bool {
494 self.for_user(user_id) >= self.for_state(state_type)
495 }
496
497 pub fn user_can_trigger_room_notification(&self, user_id: &UserId) -> bool {
502 self.for_user(user_id) >= self.notifications.room
503 }
504
505 pub fn user_can_change_user_power_level(
510 &self,
511 acting_user_id: &UserId,
512 target_user_id: &UserId,
513 ) -> bool {
514 if !self.user_can_send_state(acting_user_id, StateEventType::RoomPowerLevels) {
516 return false;
517 }
518
519 if acting_user_id == target_user_id {
521 return true;
522 }
523
524 if let Some(target_pl) = self.users.get(target_user_id).copied() {
527 self.for_user(acting_user_id) > target_pl
528 } else {
529 true
530 }
531 }
532
533 pub fn user_can_do(&self, user_id: &UserId, action: PowerLevelAction) -> bool {
535 match action {
536 PowerLevelAction::Ban => self.user_can_ban(user_id),
537 PowerLevelAction::Unban => self.user_can_unban(user_id),
538 PowerLevelAction::Invite => self.user_can_invite(user_id),
539 PowerLevelAction::Kick => self.user_can_kick(user_id),
540 PowerLevelAction::RedactOwn => self.user_can_redact_own_event(user_id),
541 PowerLevelAction::RedactOther => self.user_can_redact_event_of_other(user_id),
542 PowerLevelAction::SendMessage(message_type) => {
543 self.user_can_send_message(user_id, message_type)
544 }
545 PowerLevelAction::SendState(state_type) => {
546 self.user_can_send_state(user_id, state_type)
547 }
548 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room) => {
549 self.user_can_trigger_room_notification(user_id)
550 }
551 }
552 }
553
554 pub fn user_can_do_to_user(
557 &self,
558 acting_user_id: &UserId,
559 target_user_id: &UserId,
560 action: PowerLevelUserAction,
561 ) -> bool {
562 match action {
563 PowerLevelUserAction::Ban => self.user_can_ban_user(acting_user_id, target_user_id),
564 PowerLevelUserAction::Unban => self.user_can_unban_user(acting_user_id, target_user_id),
565 PowerLevelUserAction::Invite => self.user_can_invite(acting_user_id),
566 PowerLevelUserAction::Kick => self.user_can_kick_user(acting_user_id, target_user_id),
567 PowerLevelUserAction::ChangePowerLevel => {
568 self.user_can_change_user_power_level(acting_user_id, target_user_id)
569 }
570 }
571 }
572
573 pub fn max(&self) -> Int {
575 self.users.values().fold(self.users_default, |max_pl, user_pl| max(max_pl, *user_pl))
576 }
577}
578
579impl From<RoomPowerLevelsEventContent> for RoomPowerLevels {
580 fn from(c: RoomPowerLevelsEventContent) -> Self {
581 Self {
582 ban: c.ban,
583 events: c.events,
584 events_default: c.events_default,
585 invite: c.invite,
586 kick: c.kick,
587 redact: c.redact,
588 state_default: c.state_default,
589 users: c.users,
590 users_default: c.users_default,
591 notifications: c.notifications,
592 }
593 }
594}
595
596impl From<RedactedRoomPowerLevelsEventContent> for RoomPowerLevels {
597 fn from(c: RedactedRoomPowerLevelsEventContent) -> Self {
598 Self {
599 ban: c.ban,
600 events: c.events,
601 events_default: c.events_default,
602 invite: c.invite,
603 kick: c.kick,
604 redact: c.redact,
605 state_default: c.state_default,
606 users: c.users,
607 users_default: c.users_default,
608 notifications: NotificationPowerLevels::default(),
609 }
610 }
611}
612
613impl From<RoomPowerLevels> for RoomPowerLevelsEventContent {
614 fn from(c: RoomPowerLevels) -> Self {
615 Self {
616 ban: c.ban,
617 events: c.events,
618 events_default: c.events_default,
619 invite: c.invite,
620 kick: c.kick,
621 redact: c.redact,
622 state_default: c.state_default,
623 users: c.users,
624 users_default: c.users_default,
625 notifications: c.notifications,
626 }
627 }
628}
629
630impl From<RoomPowerLevels> for PushConditionPowerLevelsCtx {
631 fn from(c: RoomPowerLevels) -> Self {
632 Self { users: c.users, users_default: c.users_default, notifications: c.notifications }
633 }
634}
635
636#[derive(Clone, Debug, PartialEq, Eq)]
638#[non_exhaustive]
639pub enum PowerLevelAction {
640 Ban,
642
643 Unban,
645
646 Invite,
648
649 Kick,
651
652 RedactOwn,
654
655 RedactOther,
657
658 SendMessage(MessageLikeEventType),
660
661 SendState(StateEventType),
663
664 TriggerNotification(NotificationPowerLevelType),
666}
667
668#[derive(Clone, Debug, PartialEq, Eq)]
670#[non_exhaustive]
671pub enum NotificationPowerLevelType {
672 Room,
674}
675
676#[derive(Clone, Debug, PartialEq, Eq)]
678#[non_exhaustive]
679pub enum PowerLevelUserAction {
680 Ban,
682
683 Unban,
685
686 Invite,
688
689 Kick,
691
692 ChangePowerLevel,
694}
695
696#[cfg(test)]
697mod tests {
698 use std::collections::BTreeMap;
699
700 use assign::assign;
701 use js_int::int;
702 use maplit::btreemap;
703 use ruma_common::user_id;
704 use serde_json::{json, to_value as to_json_value};
705
706 use super::{default_power_level, NotificationPowerLevels, RoomPowerLevelsEventContent};
707
708 #[test]
709 fn serialization_with_optional_fields_as_none() {
710 let default = default_power_level();
711
712 let power_levels = RoomPowerLevelsEventContent {
713 ban: default,
714 events: BTreeMap::new(),
715 events_default: int!(0),
716 invite: int!(0),
717 kick: default,
718 redact: default,
719 state_default: default,
720 users: BTreeMap::new(),
721 users_default: int!(0),
722 notifications: NotificationPowerLevels::default(),
723 };
724
725 let actual = to_json_value(&power_levels).unwrap();
726 let expected = json!({});
727
728 assert_eq!(actual, expected);
729 }
730
731 #[test]
732 fn serialization_with_all_fields() {
733 let user = user_id!("@carl:example.com");
734 let power_levels_event = RoomPowerLevelsEventContent {
735 ban: int!(23),
736 events: btreemap! {
737 "m.dummy".into() => int!(23)
738 },
739 events_default: int!(23),
740 invite: int!(23),
741 kick: int!(23),
742 redact: int!(23),
743 state_default: int!(23),
744 users: btreemap! {
745 user.to_owned() => int!(23)
746 },
747 users_default: int!(23),
748 notifications: assign!(NotificationPowerLevels::new(), { room: int!(23) }),
749 };
750
751 let actual = to_json_value(&power_levels_event).unwrap();
752 let expected = json!({
753 "ban": 23,
754 "events": {
755 "m.dummy": 23
756 },
757 "events_default": 23,
758 "invite": 23,
759 "kick": 23,
760 "redact": 23,
761 "state_default": 23,
762 "users": {
763 "@carl:example.com": 23
764 },
765 "users_default": 23,
766 "notifications": {
767 "room": 23
768 },
769 });
770
771 assert_eq!(actual, expected);
772 }
773}