1use std::{
4 collections::{BTreeMap, HashSet},
5 ops::Deref,
6 sync::{Arc, Mutex, OnceLock},
7};
8
9use js_int::{int, Int};
10use ruma_common::{
11 room_version_rules::AuthorizationRules,
12 serde::{
13 btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel, from_raw_json_value,
14 DebugAsRefStr, DisplayAsRefStr, EqAsRefStr, JsonObject, OrdAsRefStr,
15 },
16 OwnedUserId, UserId,
17};
18use ruma_events::{room::power_levels::UserPowerLevel, TimelineEventType};
19use serde::de::DeserializeOwned;
20use serde_json::{from_value as from_json_value, Error};
21
22use super::Event;
23
24const DEFAULT_CREATOR_POWER_LEVEL: i32 = 100;
26
27#[derive(Debug, Clone)]
32pub struct RoomPowerLevelsEvent<E: Event> {
33 inner: Arc<RoomPowerLevelsEventInner<E>>,
34}
35
36#[derive(Debug)]
37struct RoomPowerLevelsEventInner<E: Event> {
38 event: E,
40
41 deserialized_content: OnceLock<JsonObject>,
43
44 int_fields: Mutex<BTreeMap<RoomPowerLevelsIntField, Option<Int>>>,
46
47 users: OnceLock<Option<BTreeMap<OwnedUserId, Int>>>,
49}
50
51impl<E: Event> RoomPowerLevelsEvent<E> {
52 pub fn new(event: E) -> Self {
54 Self {
55 inner: RoomPowerLevelsEventInner {
56 event,
57 deserialized_content: OnceLock::new(),
58 int_fields: Mutex::new(BTreeMap::new()),
59 users: OnceLock::new(),
60 }
61 .into(),
62 }
63 }
64
65 fn deserialized_content(&self) -> Result<&JsonObject, String> {
67 if let Some(content) = self.inner.deserialized_content.get() {
69 Ok(content)
70 } else {
71 let content = from_raw_json_value(self.content()).map_err(|error: Error| {
72 format!("malformed `m.room.power_levels` content: {error}")
73 })?;
74 Ok(self.inner.deserialized_content.get_or_init(|| content))
75 }
76 }
77
78 pub fn get_as_int(
82 &self,
83 field: RoomPowerLevelsIntField,
84 rules: &AuthorizationRules,
85 ) -> Result<Option<Int>, String> {
86 let mut int_fields =
87 self.inner.int_fields.lock().expect("we never panic while holding the mutex");
88
89 if let Some(power_level) = int_fields.get(&field) {
90 return Ok(*power_level);
91 }
92
93 let content = self.deserialized_content()?;
94
95 let Some(value) = content.get(field.as_str()) else {
96 int_fields.insert(field, None);
97 return Ok(None);
98 };
99
100 let res = if rules.integer_power_levels {
101 from_json_value(value.clone())
102 } else {
103 deserialize_v1_powerlevel(value)
104 };
105
106 let power_level = res.map(Some).map_err(|error| {
107 format!(
108 "unexpected format of `{field}` field in `content` \
109 of `m.room.power_levels` event: {error}"
110 )
111 })?;
112
113 int_fields.insert(field, power_level);
114 Ok(power_level)
115 }
116
117 pub fn get_as_int_or_default(
120 &self,
121 field: RoomPowerLevelsIntField,
122 rules: &AuthorizationRules,
123 ) -> Result<Int, String> {
124 Ok(self.get_as_int(field, rules)?.unwrap_or_else(|| field.default_value()))
125 }
126
127 fn get_as_int_map<T: Ord + DeserializeOwned>(
129 &self,
130 field: &str,
131 rules: &AuthorizationRules,
132 ) -> Result<Option<BTreeMap<T, Int>>, String> {
133 let content = self.deserialized_content()?;
134
135 let Some(value) = content.get(field) else {
136 return Ok(None);
137 };
138
139 let res = if rules.integer_power_levels {
140 from_json_value(value.clone())
141 } else {
142 btreemap_deserialize_v1_powerlevel_values(value)
143 };
144
145 res.map(Some).map_err(|error| {
146 format!(
147 "unexpected format of `{field}` field in `content` \
148 of `m.room.power_levels` event: {error}"
149 )
150 })
151 }
152
153 pub fn events(
155 &self,
156 rules: &AuthorizationRules,
157 ) -> Result<Option<BTreeMap<TimelineEventType, Int>>, String> {
158 self.get_as_int_map("events", rules)
159 }
160
161 pub fn notifications(
163 &self,
164 rules: &AuthorizationRules,
165 ) -> Result<Option<BTreeMap<String, Int>>, String> {
166 self.get_as_int_map("notifications", rules)
167 }
168
169 pub fn users(
173 &self,
174 rules: &AuthorizationRules,
175 ) -> Result<Option<&BTreeMap<OwnedUserId, Int>>, String> {
176 if let Some(users) = self.inner.users.get() {
178 Ok(users.as_ref())
179 } else {
180 let users = self.get_as_int_map("users", rules)?;
181 Ok(self.inner.users.get_or_init(|| users).as_ref())
182 }
183 }
184
185 pub fn user_power_level(
190 &self,
191 user_id: &UserId,
192 rules: &AuthorizationRules,
193 ) -> Result<Int, String> {
194 if let Some(power_level) = self.users(rules)?.as_ref().and_then(|users| users.get(user_id))
195 {
196 return Ok(*power_level);
197 }
198
199 self.get_as_int_or_default(RoomPowerLevelsIntField::UsersDefault, rules)
200 }
201
202 pub fn event_power_level(
204 &self,
205 event_type: &TimelineEventType,
206 state_key: Option<&str>,
207 rules: &AuthorizationRules,
208 ) -> Result<Int, String> {
209 let events = self.events(rules)?;
210
211 if let Some(power_level) = events.as_ref().and_then(|events| events.get(event_type)) {
212 return Ok(*power_level);
213 }
214
215 let default_field = if state_key.is_some() {
216 RoomPowerLevelsIntField::StateDefault
217 } else {
218 RoomPowerLevelsIntField::EventsDefault
219 };
220
221 self.get_as_int_or_default(default_field, rules)
222 }
223
224 pub(crate) fn int_fields_map(
227 &self,
228 rules: &AuthorizationRules,
229 ) -> Result<BTreeMap<RoomPowerLevelsIntField, Int>, String> {
230 RoomPowerLevelsIntField::ALL
231 .iter()
232 .copied()
233 .filter_map(|field| match self.get_as_int(field, rules) {
234 Ok(value) => value.map(|value| Ok((field, value))),
235 Err(error) => Some(Err(error)),
236 })
237 .collect()
238 }
239}
240
241impl<E: Event> Deref for RoomPowerLevelsEvent<E> {
242 type Target = E;
243
244 fn deref(&self) -> &Self::Target {
245 &self.inner.event
246 }
247}
248
249pub(crate) trait RoomPowerLevelsEventOptionExt {
251 fn user_power_level(
253 &self,
254 user_id: &UserId,
255 creators: &HashSet<OwnedUserId>,
256 rules: &AuthorizationRules,
257 ) -> Result<UserPowerLevel, String>;
258
259 fn get_as_int_or_default(
262 &self,
263 field: RoomPowerLevelsIntField,
264 rules: &AuthorizationRules,
265 ) -> Result<Int, String>;
266
267 fn event_power_level(
269 &self,
270 event_type: &TimelineEventType,
271 state_key: Option<&str>,
272 rules: &AuthorizationRules,
273 ) -> Result<Int, String>;
274}
275
276impl<E: Event> RoomPowerLevelsEventOptionExt for Option<RoomPowerLevelsEvent<E>> {
277 fn user_power_level(
278 &self,
279 user_id: &UserId,
280 creators: &HashSet<OwnedUserId>,
281 rules: &AuthorizationRules,
282 ) -> Result<UserPowerLevel, String> {
283 if rules.explicitly_privilege_room_creators && creators.contains(user_id) {
284 Ok(UserPowerLevel::Infinite)
285 } else if let Some(room_power_levels_event) = self {
286 room_power_levels_event.user_power_level(user_id, rules).map(Into::into)
287 } else {
288 let power_level = if creators.contains(user_id) {
289 DEFAULT_CREATOR_POWER_LEVEL.into()
290 } else {
291 RoomPowerLevelsIntField::UsersDefault.default_value()
292 };
293 Ok(power_level.into())
294 }
295 }
296
297 fn get_as_int_or_default(
298 &self,
299 field: RoomPowerLevelsIntField,
300 rules: &AuthorizationRules,
301 ) -> Result<Int, String> {
302 if let Some(room_power_levels_event) = self {
303 room_power_levels_event.get_as_int_or_default(field, rules)
304 } else {
305 Ok(field.default_value())
306 }
307 }
308
309 fn event_power_level(
310 &self,
311 event_type: &TimelineEventType,
312 state_key: Option<&str>,
313 rules: &AuthorizationRules,
314 ) -> Result<Int, String> {
315 if let Some(room_power_levels_event) = self {
316 room_power_levels_event.event_power_level(event_type, state_key, rules)
317 } else {
318 let default_field = if state_key.is_some() {
319 RoomPowerLevelsIntField::StateDefault
320 } else {
321 RoomPowerLevelsIntField::EventsDefault
322 };
323
324 Ok(default_field.default_value())
325 }
326 }
327}
328
329#[derive(DebugAsRefStr, Clone, Copy, DisplayAsRefStr, EqAsRefStr, OrdAsRefStr)]
331#[non_exhaustive]
332pub enum RoomPowerLevelsIntField {
333 UsersDefault,
335
336 EventsDefault,
338
339 StateDefault,
341
342 Ban,
344
345 Redact,
347
348 Kick,
350
351 Invite,
353}
354
355impl RoomPowerLevelsIntField {
356 pub(crate) const ALL: &[RoomPowerLevelsIntField] = &[
358 Self::UsersDefault,
359 Self::EventsDefault,
360 Self::StateDefault,
361 Self::Ban,
362 Self::Redact,
363 Self::Kick,
364 Self::Invite,
365 ];
366
367 pub fn as_str(&self) -> &str {
369 self.as_ref()
370 }
371
372 pub fn default_value(&self) -> Int {
374 match self {
375 Self::UsersDefault | Self::EventsDefault | Self::Invite => int!(0),
376 Self::StateDefault | Self::Kick | Self::Ban | Self::Redact => int!(50),
377 }
378 }
379}
380
381impl AsRef<str> for RoomPowerLevelsIntField {
382 fn as_ref(&self) -> &str {
383 match self {
384 Self::UsersDefault => "users_default",
385 Self::EventsDefault => "events_default",
386 Self::StateDefault => "state_default",
387 Self::Ban => "ban",
388 Self::Redact => "redact",
389 Self::Kick => "kick",
390 Self::Invite => "invite",
391 }
392 }
393}